summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLangston Smith <langston.smith@mapbox.com>2018-03-05 17:56:29 -0800
committerLangston Smith <langston.smith@mapbox.com>2018-03-05 17:56:29 -0800
commitaab4b971509f6f76943e2578cb13addc13ae079b (patch)
treef671cbf3dc5c7d5078423704df165eeb070f4d0f
parent6996e2d81abb8ea2ad23a6efe1502809bbe7e882 (diff)
parent136e536159a1e22aa4a92c4e6463893600b809d0 (diff)
downloadqtlocation-mapboxgl-aab4b971509f6f76943e2578cb13addc13ae079b.tar.gz
Merge branch 'master' into ls-android-readme-snapshot-dependency-line-cleanup
-rw-r--r--.gitignore4
-rw-r--r--INSTALL.md60
-rw-r--r--README.md4
-rw-r--r--appveyor.yml4
-rw-r--r--circle.yml15
-rw-r--r--cmake/core-files.cmake22
-rw-r--r--cmake/test-files.cmake1
-rw-r--r--include/mbgl/renderer/renderer.hpp2
-rw-r--r--include/mbgl/renderer/renderer_observer.hpp (renamed from src/mbgl/renderer/renderer_observer.hpp)0
-rw-r--r--include/mbgl/style/conversion/custom_geometry_source_options.hpp20
-rw-r--r--include/mbgl/style/conversion/heatmap_color_property_value.hpp46
-rw-r--r--include/mbgl/style/expression/array_assertion.hpp3
-rw-r--r--include/mbgl/style/expression/assertion.hpp2
-rw-r--r--include/mbgl/style/expression/at.hpp2
-rw-r--r--include/mbgl/style/expression/boolean_operator.hpp2
-rw-r--r--include/mbgl/style/expression/case.hpp1
-rw-r--r--include/mbgl/style/expression/coalesce.hpp1
-rw-r--r--include/mbgl/style/expression/coercion.hpp1
-rw-r--r--include/mbgl/style/expression/compound_expression.hpp15
-rw-r--r--include/mbgl/style/expression/equals.hpp1
-rw-r--r--include/mbgl/style/expression/expression.hpp11
-rw-r--r--include/mbgl/style/expression/interpolate.hpp3
-rw-r--r--include/mbgl/style/expression/let.hpp4
-rw-r--r--include/mbgl/style/expression/literal.hpp14
-rw-r--r--include/mbgl/style/expression/match.hpp4
-rw-r--r--include/mbgl/style/expression/step.hpp2
-rw-r--r--include/mbgl/style/expression/value.hpp1
-rw-r--r--include/mbgl/style/function/convert.hpp1
-rw-r--r--include/mbgl/style/heatmap_color_property_value.hpp49
-rw-r--r--include/mbgl/style/layer.hpp3
-rw-r--r--include/mbgl/style/layer_type.hpp1
-rw-r--r--include/mbgl/style/layers/heatmap_layer.hpp86
-rw-r--r--include/mbgl/style/layers/layer.hpp.ejs3
-rw-r--r--include/mbgl/style/sources/custom_geometry_source.hpp2
-rw-r--r--include/mbgl/util/run_loop.hpp54
-rw-r--r--include/mbgl/util/thread.hpp21
-rw-r--r--include/mbgl/util/tileset.hpp10
m---------mapbox-gl-js0
-rw-r--r--package.json2
-rw-r--r--platform/android/CHANGELOG.md46
-rw-r--r--platform/android/DISTRIBUTE.md2
-rw-r--r--platform/android/MapboxGLAndroidSDK/.gitignore2
-rw-r--r--platform/android/MapboxGLAndroidSDK/build.gradle5
-rw-r--r--platform/android/MapboxGLAndroidSDK/lint-baseline-local.xml37
-rw-r--r--platform/android/MapboxGLAndroidSDK/lint/lint-baseline-ci.xml44
-rw-r--r--platform/android/MapboxGLAndroidSDK/proguard-rules.pro5
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/BaseGestureDetector.java160
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/MoveGestureDetector.java185
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/RotateGestureDetector.java180
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/ShoveGestureDetector.java214
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/TwoFingerGestureDetector.java224
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/package-info.java4
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/EmptyLocationSource.java107
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/Mapbox.java31
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/camera/CameraPosition.java2
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/GeometryConstants.java14
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/MapboxConstants.java29
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/MyBearingTracking.java7
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/geometry/LatLngBounds.java112
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationSource.java199
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/package-info.java4
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/AttributionDialogManager.java18
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Events.java36
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapGestureDetector.java1079
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapKeyListener.java8
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java126
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxEventWrapper.java57
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxMap.java214
-rwxr-xr-xplatform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMapView.java4
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Projection.java11
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/TrackingSettings.java8
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Transform.java34
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/renderer/textureview/TextureViewRenderThread.java14
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/CompassView.java4
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/MyLocationView.java29
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/storage/FileSource.java5
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/HeatmapLayer.java221
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/HillshadeLayer.java2
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/PropertyFactory.java132
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/CustomGeometrySource.java12
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/CustomGeometrySourceOptions.java31
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/GeoJsonOptions.java2
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/GeoJsonSource.java6
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/GeometryTileProvider.java2
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/ImageSource.java11
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/VectorSource.java2
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/utils/MathUtils.java49
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/res/layout/mapbox_mapview_internal.xml7
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/res/values-iw/strings.xml (renamed from platform/android/MapboxGLAndroidSDK/src/main/res/values-he/strings.xml)0
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/res/values/dimens.xml9
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/MapboxTest.java2
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/geometry/LatLngBoundsTest.java131
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/maps/MapTouchListenersTest.java132
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/maps/MapboxMapTest.java2
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/build.gradle12
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/maps/OrientationTest.java12
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/action/WaitAction.java39
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/activity/BaseActivityTest.java41
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/maps/widgets/CompassViewTest.java2
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/storage/FileSourceTest.java129
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/GeoJsonSourceTests.java8
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/HeatmapLayerTest.java626
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml21
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/FeatureOverviewActivity.java4
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/annotation/AnimatedMarkerActivity.java283
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/annotation/AnimatedSymbolLayerActivity.java449
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/feature/QueryRenderedFeaturesBoxCountActivity.java12
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/feature/QueryRenderedFeaturesBoxHighlightActivity.java5
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/feature/QueryRenderedFeaturesBoxSymbolCountActivity.java2
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/feature/QueryRenderedFeaturesPropertiesActivity.java15
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/feature/QuerySourceFeaturesActivity.java9
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/CustomSpriteActivity.java18
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/FillExtrusionActivity.java4
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/GridSourceActivity.java21
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/HeatmapLayerActivity.java216
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/RuntimeStyleActivity.java9
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/SymbolGeneratorActivity.java20
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/SymbolLayerActivity.java11
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/ZoomFunctionSymbolLayerActivity.java18
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/userlocation/BaseLocationActivity.java4
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/userlocation/MockLocationEngine.java4
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/userlocation/MyLocationDrawableActivity.java2
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/userlocation/MyLocationTintActivity.java2
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/userlocation/MyLocationTrackingModeActivity.java2
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/utils/GeoParseUtil.java16
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_animated_marker.xml6
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_heatmaplayer.xml14
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/descriptions.xml3
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/titles.xml3
-rw-r--r--platform/android/build.gradle1
-rw-r--r--platform/android/config.cmake4
-rw-r--r--platform/android/gradle/dependencies.gradle36
-rw-r--r--platform/android/scripts/exclude-activity-gen.json9
-rwxr-xr-xplatform/android/scripts/generate-style-code.js3
-rw-r--r--platform/android/src/android_renderer_frontend.cpp4
-rw-r--r--platform/android/src/android_renderer_frontend.hpp2
-rw-r--r--platform/android/src/conversion/collection.hpp1
-rw-r--r--platform/android/src/example_custom_layer.cpp200
-rw-r--r--platform/android/src/file_source.cpp15
-rw-r--r--platform/android/src/file_source.hpp2
-rw-r--r--platform/android/src/geojson/conversion/geometry.hpp130
-rw-r--r--platform/android/src/geojson/feature.cpp18
-rw-r--r--platform/android/src/geojson/feature.hpp8
-rw-r--r--platform/android/src/geojson/feature_collection.cpp28
-rw-r--r--platform/android/src/geojson/feature_collection.hpp4
-rw-r--r--platform/android/src/geojson/geometry.cpp2
-rw-r--r--platform/android/src/geojson/geometry.hpp2
-rw-r--r--platform/android/src/geojson/line_string.cpp29
-rw-r--r--platform/android/src/geojson/line_string.hpp6
-rw-r--r--platform/android/src/geojson/multi_line_string.cpp22
-rw-r--r--platform/android/src/geojson/multi_line_string.hpp6
-rw-r--r--platform/android/src/geojson/multi_point.cpp10
-rw-r--r--platform/android/src/geojson/multi_point.hpp4
-rw-r--r--platform/android/src/geojson/multi_polygon.cpp16
-rw-r--r--platform/android/src/geojson/multi_polygon.hpp4
-rw-r--r--platform/android/src/geojson/point.cpp41
-rw-r--r--platform/android/src/geojson/point.hpp10
-rw-r--r--platform/android/src/geojson/polygon.cpp12
-rw-r--r--platform/android/src/geojson/polygon.hpp6
-rw-r--r--platform/android/src/geojson/position.cpp27
-rw-r--r--platform/android/src/geojson/position.hpp27
-rwxr-xr-xplatform/android/src/jni.cpp2
-rw-r--r--platform/android/src/map/camera_position.cpp4
-rw-r--r--platform/android/src/map/image.cpp4
-rwxr-xr-xplatform/android/src/native_map_view.cpp2
-rw-r--r--platform/android/src/run_loop.cpp7
-rw-r--r--platform/android/src/style/layers/heatmap_layer.cpp134
-rw-r--r--platform/android/src/style/layers/heatmap_layer.hpp50
-rw-r--r--platform/android/src/style/layers/layer.cpp1
-rw-r--r--platform/android/src/style/layers/layers.cpp4
-rw-r--r--platform/android/src/style/sources/custom_geometry_source.cpp2
-rw-r--r--platform/android/src/style/sources/image_source.cpp8
-rw-r--r--platform/android/src/style/sources/image_source.hpp2
-rw-r--r--platform/darwin/docs/guides/For Style Authors.md.ejs3
-rwxr-xr-xplatform/darwin/scripts/generate-style-code.js11
-rw-r--r--platform/darwin/scripts/style-spec-overrides-v8.json10
-rw-r--r--platform/darwin/src/MGLAbstractShapeSource.h25
-rw-r--r--platform/darwin/src/MGLAbstractShapeSource.mm19
-rw-r--r--platform/darwin/src/MGLHeatmapStyleLayer.h189
-rw-r--r--platform/darwin/src/MGLHeatmapStyleLayer.mm210
-rw-r--r--platform/darwin/src/MGLMapSnapshotter.mm339
-rw-r--r--platform/darwin/src/MGLRendererFrontend.h4
-rw-r--r--platform/darwin/src/MGLStyle.mm4
-rw-r--r--platform/darwin/src/MGLStyleLayer.mm.ejs4
-rw-r--r--platform/darwin/src/MGLStyleValue_Private.h36
-rw-r--r--platform/darwin/src/MGLTileSource.h44
-rw-r--r--platform/darwin/src/MGLTileSource.mm30
-rw-r--r--platform/darwin/src/MGLVectorStyleLayer.h8
-rw-r--r--platform/darwin/src/run_loop.cpp7
-rw-r--r--platform/darwin/test/MGLCoordinateFormatterTests.m21
-rw-r--r--platform/darwin/test/MGLDocumentationExampleTests.swift20
-rw-r--r--platform/darwin/test/MGLExpressionTests.mm38
-rw-r--r--platform/darwin/test/MGLFeatureTests.mm27
-rw-r--r--platform/darwin/test/MGLHeatmapColorTests.mm61
-rw-r--r--platform/darwin/test/MGLHeatmapStyleLayerTests.mm296
-rw-r--r--platform/darwin/test/MGLStyleLayerTests.mm.ejs2
-rw-r--r--platform/darwin/test/MGLTileSetTests.mm31
-rw-r--r--platform/default/mbgl/storage/offline_database.cpp508
-rw-r--r--platform/default/mbgl/storage/offline_database.hpp20
-rw-r--r--platform/default/run_loop.cpp7
-rw-r--r--platform/default/sqlite3.cpp320
-rw-r--r--platform/default/sqlite3.hpp101
-rw-r--r--platform/ios/CHANGELOG.md12
-rw-r--r--platform/ios/Mapbox-iOS-SDK-nightly-dynamic.podspec2
-rw-r--r--platform/ios/Mapbox-iOS-SDK-symbols.podspec2
-rw-r--r--platform/ios/Mapbox-iOS-SDK.podspec2
-rw-r--r--platform/ios/app/MBXSnapshotsViewController.m3
-rw-r--r--platform/ios/app/MBXViewController.m4
-rw-r--r--platform/ios/docs/guides/For Style Authors.md4
-rw-r--r--platform/ios/ios.xcodeproj/project.pbxproj51
-rw-r--r--platform/ios/jazzy.yml1
-rw-r--r--platform/ios/src/MGLCameraChangeReason.h61
-rw-r--r--platform/ios/src/MGLLocationManager.m6
-rw-r--r--platform/ios/src/MGLMapView.mm190
-rw-r--r--platform/ios/src/MGLMapViewDelegate.h119
-rw-r--r--platform/ios/src/MGLMapboxEvents.m3
-rw-r--r--platform/ios/src/MGLTelemetryConfig.h18
-rw-r--r--platform/ios/src/MGLTelemetryConfig.m35
-rw-r--r--platform/ios/src/Mapbox.h1
-rw-r--r--platform/ios/test/MGLMapViewDelegateIntegrationTests.swift11
-rw-r--r--platform/ios/test/MGLMapViewLayoutTests.m2
-rw-r--r--platform/ios/uitest/MapViewTests.m7
-rw-r--r--platform/macos/CHANGELOG.md7
-rw-r--r--platform/macos/app/Assets.xcassets/Layers/heatmap.imageset/Contents.json16
-rw-r--r--platform/macos/app/Assets.xcassets/Layers/heatmap.imageset/heatmap.pdfbin0 -> 1262 bytes
-rw-r--r--platform/macos/app/MapDocument.m8
-rw-r--r--platform/macos/app/StyleLayerIconTransformer.m3
-rw-r--r--platform/macos/app/heatmap.json809
-rw-r--r--platform/macos/app/resources/heatmap.svg72
-rw-r--r--platform/macos/docs/guides/For Style Authors.md4
-rw-r--r--platform/macos/jazzy.yml1
-rw-r--r--platform/macos/macos.xcodeproj/project.pbxproj20
-rw-r--r--platform/macos/src/Mapbox.h1
-rw-r--r--platform/node/src/node_expression.cpp27
-rw-r--r--platform/node/src/node_expression.hpp3
-rw-r--r--platform/node/src/node_map.cpp5
-rw-r--r--platform/node/test/expression.test.js60
-rw-r--r--platform/node/test/ignores.json23
-rw-r--r--platform/qt/README.md4
-rw-r--r--platform/qt/app/mapwindow.cpp5
-rw-r--r--platform/qt/config.qdocconf2
-rw-r--r--platform/qt/include/qmapboxgl.hpp24
-rw-r--r--platform/qt/qt.cmake8
-rw-r--r--platform/qt/src/qmapbox.cpp14
-rw-r--r--platform/qt/src/qmapboxgl.cpp383
-rw-r--r--platform/qt/src/qmapboxgl_map_observer.cpp95
-rw-r--r--platform/qt/src/qmapboxgl_map_observer.hpp45
-rw-r--r--platform/qt/src/qmapboxgl_map_renderer.cpp86
-rw-r--r--platform/qt/src/qmapboxgl_map_renderer.hpp63
-rw-r--r--platform/qt/src/qmapboxgl_p.hpp77
-rw-r--r--platform/qt/src/qmapboxgl_renderer_backend.cpp49
-rw-r--r--platform/qt/src/qmapboxgl_renderer_backend.hpp34
-rw-r--r--platform/qt/src/qmapboxgl_renderer_frontend_p.cpp37
-rw-r--r--platform/qt/src/qmapboxgl_renderer_frontend_p.hpp35
-rw-r--r--platform/qt/src/qmapboxgl_renderer_observer.hpp51
-rw-r--r--[-rwxr-xr-x]platform/qt/src/qt_logging.cpp0
-rw-r--r--platform/qt/src/run_loop.cpp7
-rw-r--r--platform/qt/src/sqlite3.cpp213
-rw-r--r--platform/qt/test/qmapboxgl.test.cpp4
-rwxr-xr-xscripts/generate-shaders.js3
-rwxr-xr-xscripts/generate-style-code.js2
-rw-r--r--scripts/style-spec.js2
-rw-r--r--src/mbgl/annotation/render_annotation_source.cpp4
-rw-r--r--src/mbgl/annotation/render_annotation_source.hpp2
-rw-r--r--src/mbgl/geometry/dem_data.cpp16
-rw-r--r--src/mbgl/geometry/dem_data.hpp3
-rw-r--r--src/mbgl/gl/color_mode.hpp4
-rw-r--r--src/mbgl/gl/context.cpp43
-rw-r--r--src/mbgl/gl/context.hpp24
-rw-r--r--src/mbgl/gl/types.hpp9
-rw-r--r--src/mbgl/layout/symbol_projection.cpp5
-rw-r--r--src/mbgl/programs/attributes.hpp7
-rw-r--r--src/mbgl/programs/collision_box_program.hpp1
-rw-r--r--src/mbgl/programs/heatmap_program.cpp7
-rw-r--r--src/mbgl/programs/heatmap_program.hpp49
-rw-r--r--src/mbgl/programs/heatmap_texture_program.cpp7
-rw-r--r--src/mbgl/programs/heatmap_texture_program.hpp43
-rw-r--r--src/mbgl/programs/hillshade_prepare_program.hpp2
-rw-r--r--src/mbgl/programs/programs.hpp6
-rw-r--r--src/mbgl/programs/uniforms.hpp6
-rw-r--r--src/mbgl/renderer/buckets/heatmap_bucket.cpp98
-rw-r--r--src/mbgl/renderer/buckets/heatmap_bucket.hpp40
-rw-r--r--src/mbgl/renderer/buckets/hillshade_bucket.cpp2
-rw-r--r--src/mbgl/renderer/buckets/hillshade_bucket.hpp5
-rw-r--r--src/mbgl/renderer/layers/render_custom_layer.cpp7
-rw-r--r--src/mbgl/renderer/layers/render_heatmap_layer.cpp178
-rw-r--r--src/mbgl/renderer/layers/render_heatmap_layer.hpp48
-rw-r--r--src/mbgl/renderer/layers/render_hillshade_layer.cpp12
-rw-r--r--src/mbgl/renderer/layers/render_hillshade_layer.hpp2
-rw-r--r--src/mbgl/renderer/layers/render_symbol_layer.cpp3
-rw-r--r--src/mbgl/renderer/render_layer.cpp3
-rw-r--r--src/mbgl/renderer/render_source.hpp2
-rw-r--r--src/mbgl/renderer/renderer.cpp4
-rw-r--r--src/mbgl/renderer/renderer_impl.cpp17
-rw-r--r--src/mbgl/renderer/renderer_impl.hpp2
-rw-r--r--src/mbgl/renderer/sources/render_custom_geometry_source.cpp4
-rw-r--r--src/mbgl/renderer/sources/render_custom_geometry_source.hpp2
-rw-r--r--src/mbgl/renderer/sources/render_geojson_source.cpp4
-rw-r--r--src/mbgl/renderer/sources/render_geojson_source.hpp2
-rw-r--r--src/mbgl/renderer/sources/render_image_source.hpp2
-rw-r--r--src/mbgl/renderer/sources/render_raster_dem_source.cpp21
-rw-r--r--src/mbgl/renderer/sources/render_raster_dem_source.hpp9
-rw-r--r--src/mbgl/renderer/sources/render_raster_source.cpp19
-rw-r--r--src/mbgl/renderer/sources/render_raster_source.hpp4
-rw-r--r--src/mbgl/renderer/sources/render_vector_source.cpp19
-rw-r--r--src/mbgl/renderer/sources/render_vector_source.hpp4
-rw-r--r--src/mbgl/renderer/tile_pyramid.cpp9
-rw-r--r--src/mbgl/renderer/tile_pyramid.hpp2
-rw-r--r--src/mbgl/shaders/collision_circle.cpp8
-rw-r--r--src/mbgl/shaders/heatmap.cpp128
-rw-r--r--src/mbgl/shaders/heatmap.hpp16
-rw-r--r--src/mbgl/shaders/heatmap_texture.cpp42
-rw-r--r--src/mbgl/shaders/heatmap_texture.hpp16
-rw-r--r--src/mbgl/shaders/hillshade_prepare.cpp9
-rw-r--r--src/mbgl/style/conversion/layer.cpp3
-rw-r--r--src/mbgl/style/conversion/make_property_setters.hpp13
-rw-r--r--src/mbgl/style/conversion/property_setter.hpp1
-rw-r--r--src/mbgl/style/conversion/tileset.cpp12
-rw-r--r--src/mbgl/style/expression/array_assertion.cpp19
-rw-r--r--src/mbgl/style/expression/assertion.cpp4
-rw-r--r--src/mbgl/style/expression/coercion.cpp7
-rw-r--r--src/mbgl/style/expression/compound_expression.cpp42
-rw-r--r--src/mbgl/style/expression/interpolate.cpp24
-rw-r--r--src/mbgl/style/expression/let.cpp15
-rw-r--r--src/mbgl/style/expression/literal.cpp8
-rw-r--r--src/mbgl/style/expression/match.cpp36
-rw-r--r--src/mbgl/style/expression/step.cpp12
-rw-r--r--src/mbgl/style/expression/value.cpp33
-rw-r--r--src/mbgl/style/layers/heatmap_layer.cpp239
-rw-r--r--src/mbgl/style/layers/heatmap_layer_impl.cpp15
-rw-r--r--src/mbgl/style/layers/heatmap_layer_impl.hpp21
-rw-r--r--src/mbgl/style/layers/heatmap_layer_properties.cpp9
-rw-r--r--src/mbgl/style/layers/heatmap_layer_properties.hpp40
-rw-r--r--src/mbgl/style/layers/layer.cpp.ejs12
-rw-r--r--src/mbgl/style/layers/layer_properties.hpp.ejs1
-rw-r--r--src/mbgl/style/paint_property.hpp23
-rw-r--r--src/mbgl/style/style_impl.cpp6
-rw-r--r--src/mbgl/text/placement.cpp17
-rw-r--r--src/mbgl/tile/custom_geometry_tile.cpp2
-rw-r--r--src/mbgl/tile/raster_dem_tile.cpp3
-rw-r--r--src/mbgl/tile/raster_dem_tile.hpp1
-rw-r--r--src/mbgl/tile/raster_dem_tile_worker.cpp4
-rw-r--r--src/mbgl/tile/raster_dem_tile_worker.hpp3
-rw-r--r--src/mbgl/util/offscreen_texture.cpp22
-rw-r--r--src/mbgl/util/offscreen_texture.hpp6
-rw-r--r--src/mbgl/util/tile_range.hpp60
-rw-r--r--src/mbgl/util/tiny_sdf.cpp2
-rw-r--r--test/fixtures/map/prefetch/expected.pngbin2198 -> 0 bytes
-rw-r--r--test/fixtures/map/prefetch/tile.png (renamed from test/fixtures/map/prefetch/tile_green.png)bin659 -> 659 bytes
-rw-r--r--test/fixtures/map/prefetch/tile_red.pngbin659 -> 0 bytes
-rw-r--r--test/fixtures/offline_database/satellite_test.dbbin0 -> 114688 bytes
-rw-r--r--test/geometry/dem_data.test.cpp39
-rw-r--r--test/map/map.test.cpp67
-rw-r--r--test/map/prefetch.test.cpp40
-rw-r--r--test/src/mbgl/test/stub_map_observer.hpp49
-rw-r--r--test/storage/offline_database.test.cpp81
-rw-r--r--test/storage/sqlite.test.cpp32
-rw-r--r--test/style/conversion/tileset.test.cpp10
-rw-r--r--test/tile/raster_dem_tile.test.cpp2
-rw-r--r--test/util/run_loop.test.cpp14
-rw-r--r--test/util/tile_range.test.cpp13
361 files changed, 10271 insertions, 4444 deletions
diff --git a/.gitignore b/.gitignore
index 0f4fa77858..b32baaac09 100644
--- a/.gitignore
+++ b/.gitignore
@@ -17,6 +17,8 @@ xcuserdata
/test/fixtures/api/2.png
/test/fixtures/offline_database/offline.db
/test/fixtures/offline_database/offline.db-*
+/test/fixtures/offline_database/satellite.db
+/test/fixtures/offline_database/satellite.db-*
/test/fixtures/offline_database/invalid.db
/test/fixtures/offline_database/invalid.db-*
/test/fixtures/offline_database/migrated.db
@@ -38,4 +40,4 @@ test/fixtures/storage/assets.zip
# Generated list files from code generation
/scripts/generate-cmake-files.list
/scripts/generate-shaders.list
-/scripts/generate-style-code.list \ No newline at end of file
+/scripts/generate-style-code.list
diff --git a/INSTALL.md b/INSTALL.md
index fb619770a1..e5d059e1bb 100644
--- a/INSTALL.md
+++ b/INSTALL.md
@@ -7,50 +7,38 @@ that you can download instantly and get started with fast](https://www.mapbox.co
Still with us? These are the instructions you'll need to build Mapbox GL Native
from source on a variety of platforms and set up a development environment.
-Your journey will start with getting the source code, then installing the
-dependencies, and then setting up a development environment, which varies
-depending on your operating system and what platform you want to develop for.
+Your journey will start with installing dependencies, then getting the source code, and
+then setting up a development environment, which varies depending on your
+operating system and what platform you want to develop for.
-## 1: Getting the source
+## 1: Installing dependencies
-Clone the git repository:
+### macOS
- git clone https://github.com/mapbox/mapbox-gl-native.git
- cd mapbox-gl-native
+ 1. Install [Xcode](https://developer.apple.com/xcode/)
+ 2. Launch Xcode and install any updates
+ 3. Install [Homebrew](http://brew.sh)
+ 4. Install [Node.js](https://nodejs.org/), [CMake](https://cmake.org/), and [ccache](https://ccache.samba.org) with `brew install nodejs cmake ccache`
+ 5. Install [xcpretty](https://github.com/supermarin/xcpretty) with `[sudo] gem install xcpretty` (optional, used for prettifying command line builds)
-## 2: Installing dependencies
+### Linux
-These dependencies are required for all operating systems and all platform
-targets.
-
- - Modern C++ compiler that supports `-std=c++14`\*
- - clang++ 3.5 or later _or_
- - g++-4.9 or later
- - [CMake](https://cmake.org/) 3.1 or later (for build only)
- - [cURL](https://curl.haxx.se) (for build only)
- - [Node.js](https://nodejs.org/) 4.2.1 or later (for build only)
- - [`pkg-config`](https://wiki.freedesktop.org/www/Software/pkg-config/) (for build only)
-
-**Note**: We partially support C++14 because GCC 4.9 does not fully implement the
-final draft of the C++14 standard. More information in [DEVELOPING.md](DEVELOPING.md).
-
-Depending on your operating system and target, you'll need additional
-dependencies:
-
-### Additional dependencies for Linux
+Install the following:
+ - `clang++` 3.5 or later or `g++` 4.9 or later
+ - [git](https://git-scm.com/)
+ - [CMake](https://cmake.org/) 3.1 or later
+ - [cURL](https://curl.haxx.se)
+ - [Node.js](https://nodejs.org/) 4.2.1 or later
- [`libcurl`](http://curl.haxx.se/libcurl/) (depends on OpenSSL)
+ - [ccache](https://ccache.samba.org) (optional, improves recompilation performance)
-### Additional dependencies for macOS
-
- - Apple Command Line Tools (available at [Apple Developer](https://developer.apple.com/download/more/))
- - [Homebrew](http://brew.sh)
- - [Cask](http://caskroom.io/) (if building for Android)
- - [xcpretty](https://github.com/supermarin/xcpretty) (`gem install xcpretty`)
+## 2: Getting the source
-### Optional dependencies
+ Clone the git repository:
-- [ccache](https://ccache.samba.org) (for build only; improves recompilation performance)
+ git clone https://github.com/mapbox/mapbox-gl-native.git
+ cd mapbox-gl-native
## 3: Setting up a development environment & building
@@ -58,8 +46,8 @@ See the relevant SDK documentation for next steps:
* [Maps SDK for Android](platform/android/)
* [Maps SDK for iOS](platform/ios/)
-* [Maps SDK for iOS](platform/macos/)
-* [Mapbox Qt SDK](platform/qt/)
+* [Maps SDK for macOS](platform/macos/)
+* [Maps SDK for Qt](platform/qt/)
* [Mapbox GL Native on Linux](platform/linux/)
* [node-mapbox-gl-native](platform/node/)
diff --git a/README.md b/README.md
index 32bfdf3b51..8815e506a7 100644
--- a/README.md
+++ b/README.md
@@ -13,7 +13,7 @@ This repository hosts the cross-platform Mapbox GL Native library, plus convenie
| [Mapbox Maps SDK for iOS](platform/ios/) | Objective-C or Swift | [![Bitrise](https://www.bitrise.io/app/7514e4cf3da2cc57.svg?token=OwqZE5rSBR9MVWNr_lf4sA&branch=master)](https://www.bitrise.io/app/7514e4cf3da2cc57) |
| [Mapbox Maps SDK for macOS](platform/macos/) | Objective-C, Swift, or AppleScript | [![Bitrise](https://www.bitrise.io/app/155ef7da24b38dcd.svg?token=4KSOw_gd6WxTnvGE2rMttg&branch=master)](https://www.bitrise.io/app/155ef7da24b38dcd) |
| [node-mapbox-gl-native](platform/node/) | Node.js | [![Circle CI build status](https://circleci.com/gh/mapbox/mapbox-gl-native.svg?style=shield)](https://circleci.com/gh/mapbox/workflows/mapbox-gl-native/tree/master) |
-| [Mapbox Qt SDK](platform/qt) | C++03 | [![Circle CI build status](https://circleci.com/gh/mapbox/mapbox-gl-native.svg?style=shield)](https://circleci.com/gh/mapbox/workflows/mapbox-gl-native/tree/master) [![AppVeyor CI build status](https://ci.appveyor.com/api/projects/status/3q12kbcooc6df8uc?svg=true)](https://ci.appveyor.com/project/Mapbox/mapbox-gl-native) |
+| [Mapbox Maps SDK for Qt](platform/qt) | C++03 | [![Circle CI build status](https://circleci.com/gh/mapbox/mapbox-gl-native.svg?style=shield)](https://circleci.com/gh/mapbox/workflows/mapbox-gl-native/tree/master) [![AppVeyor CI build status](https://ci.appveyor.com/api/projects/status/3q12kbcooc6df8uc?svg=true)](https://ci.appveyor.com/project/Mapbox/mapbox-gl-native) |
Additional Mapbox GL Native–based libraries for **hybrid applications** are developed outside of this repository:
@@ -21,7 +21,7 @@ Additional Mapbox GL Native–based libraries for **hybrid applications** are de
| ---------------------------------------- | --------|-----|------------ |
| [React Native](https://github.com/mapbox/react-native-mapbox-gl/) ([npm](https://www.npmjs.com/package/react-native-mapbox-gl)) | :white_check_mark: | :white_check_mark: | Mapbox |
| [Apache Cordova](http://plugins.telerik.com/cordova/plugin/mapbox/) ([npm](https://www.npmjs.com/package/cordova-plugin-mapbox)) | :white_check_mark: | :white_check_mark: | Telerik |
-| [NativeScript](http://plugins.telerik.com/nativescript/plugin/mapbox/) ([npm](https://www.npmjs.com/package/nativescript-mapbox/)) | :white_check_mark: | :white_check_mark: | Telerik |
+| [NativeScript](https://market.nativescript.org/plugins/nativescript-mapbox/) ([npm](https://www.npmjs.com/package/nativescript-mapbox/)) | :white_check_mark: | :white_check_mark: | Telerik |
| [Xamarin](https://components.xamarin.com/view/mapboxsdk/) | :white_check_mark: | :white_check_mark: | Xamarin |
If your platform or hybrid application framework isn’t listed here, consider embedding [Mapbox GL JS](https://github.com/mapbox/mapbox-gl-js) using the standard Web capabilities on your platform.
diff --git a/appveyor.yml b/appveyor.yml
index 8013c669f6..d31160261f 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -7,10 +7,6 @@ environment:
shallow_clone: true
-branches:
- only:
- - master
-
cache:
- '%APPVEYOR_BUILD_FOLDER%\mason_packages'
- '%APPVEYOR_BUILD_FOLDER%\LLVM-5.0.1-win64.exe'
diff --git a/circle.yml b/circle.yml
index c7e1e519c7..122d8f3b9a 100644
--- a/circle.yml
+++ b/circle.yml
@@ -339,21 +339,10 @@ jobs:
shell: /bin/bash -euo pipefail
command: |
gcloud firebase test android models list
- (gcloud firebase test android run --type instrumentation \
+ gcloud firebase test android run --type instrumentation \
--app platform/android/MapboxGLAndroidSDKTestApp/build/outputs/apk/debug/MapboxGLAndroidSDKTestApp-debug.apk \
--test platform/android/MapboxGLAndroidSDKTestApp/build/outputs/apk/androidTest/debug/MapboxGLAndroidSDKTestApp-debug-androidTest.apk \
- --device-ids sailfish --os-version-ids 26 --locales en --orientations portrait --timeout 20m \
- 2>&1 | tee firebase.log) || EXIT_CODE=$?
-
- FIREBASE_TEST_BUCKET=$(sed -n 's|^.*\[https://console.developers.google.com/storage/browser/\([^]]*\).*|gs://\1|p' firebase.log)
- echo "Downloading from: ${FIREBASE_TEST_BUCKET}"
- gsutil -m cp -n -R -Z "$FIREBASE_TEST_BUCKET*" platform/android/MapboxGLAndroidSDKTestApp/build/outputs/apk/debug
-
- echo "Try running ndk-stack on downloaded logcat to symbolicate the stacktraces:"
- find platform/android/MapboxGLAndroidSDKTestApp/build/outputs/apk/debug -type f -name "logcat" -print0 | \
- xargs -0 -I '{}' ${ANDROID_NDK_HOME}/ndk-stack -sym build/android-arm-v7/Debug -dump {}
-
- exit ${EXIT_CODE:-0}
+ --device-ids sailfish --os-version-ids 26 --locales en --orientations portrait --timeout 20m
- store_artifacts:
path: platform/android/MapboxGLAndroidSDKTestApp/build/outputs/apk/debug
destination: .
diff --git a/cmake/core-files.cmake b/cmake/core-files.cmake
index 24d5262799..f24482e301 100644
--- a/cmake/core-files.cmake
+++ b/cmake/core-files.cmake
@@ -146,6 +146,10 @@ set(MBGL_CORE_FILES
src/mbgl/programs/fill_extrusion_program.hpp
src/mbgl/programs/fill_program.cpp
src/mbgl/programs/fill_program.hpp
+ src/mbgl/programs/heatmap_program.cpp
+ src/mbgl/programs/heatmap_program.hpp
+ src/mbgl/programs/heatmap_texture_program.cpp
+ src/mbgl/programs/heatmap_texture_program.hpp
src/mbgl/programs/hillshade_prepare_program.cpp
src/mbgl/programs/hillshade_prepare_program.hpp
src/mbgl/programs/hillshade_program.cpp
@@ -170,6 +174,7 @@ set(MBGL_CORE_FILES
include/mbgl/renderer/renderer.hpp
include/mbgl/renderer/renderer_backend.hpp
include/mbgl/renderer/renderer_frontend.hpp
+ include/mbgl/renderer/renderer_observer.hpp
src/mbgl/renderer/backend_scope.cpp
src/mbgl/renderer/bucket.hpp
src/mbgl/renderer/bucket_parameters.cpp
@@ -206,7 +211,6 @@ set(MBGL_CORE_FILES
src/mbgl/renderer/renderer_backend.cpp
src/mbgl/renderer/renderer_impl.cpp
src/mbgl/renderer/renderer_impl.hpp
- src/mbgl/renderer/renderer_observer.hpp
src/mbgl/renderer/style_diff.cpp
src/mbgl/renderer/style_diff.hpp
src/mbgl/renderer/tile_mask.hpp
@@ -225,6 +229,8 @@ set(MBGL_CORE_FILES
src/mbgl/renderer/buckets/fill_bucket.hpp
src/mbgl/renderer/buckets/fill_extrusion_bucket.cpp
src/mbgl/renderer/buckets/fill_extrusion_bucket.hpp
+ src/mbgl/renderer/buckets/heatmap_bucket.cpp
+ src/mbgl/renderer/buckets/heatmap_bucket.hpp
src/mbgl/renderer/buckets/hillshade_bucket.cpp
src/mbgl/renderer/buckets/hillshade_bucket.hpp
src/mbgl/renderer/buckets/line_bucket.cpp
@@ -245,6 +251,8 @@ set(MBGL_CORE_FILES
src/mbgl/renderer/layers/render_fill_extrusion_layer.hpp
src/mbgl/renderer/layers/render_fill_layer.cpp
src/mbgl/renderer/layers/render_fill_layer.hpp
+ src/mbgl/renderer/layers/render_heatmap_layer.cpp
+ src/mbgl/renderer/layers/render_heatmap_layer.hpp
src/mbgl/renderer/layers/render_hillshade_layer.cpp
src/mbgl/renderer/layers/render_hillshade_layer.hpp
src/mbgl/renderer/layers/render_line_layer.cpp
@@ -297,6 +305,10 @@ set(MBGL_CORE_FILES
src/mbgl/shaders/fill_outline_pattern.hpp
src/mbgl/shaders/fill_pattern.cpp
src/mbgl/shaders/fill_pattern.hpp
+ src/mbgl/shaders/heatmap.cpp
+ src/mbgl/shaders/heatmap.hpp
+ src/mbgl/shaders/heatmap_texture.cpp
+ src/mbgl/shaders/heatmap_texture.hpp
src/mbgl/shaders/hillshade.cpp
src/mbgl/shaders/hillshade.hpp
src/mbgl/shaders/hillshade_prepare.cpp
@@ -349,6 +361,7 @@ set(MBGL_CORE_FILES
include/mbgl/style/data_driven_property_value.hpp
include/mbgl/style/filter.hpp
include/mbgl/style/filter_evaluator.hpp
+ include/mbgl/style/heatmap_color_property_value.hpp
include/mbgl/style/image.hpp
include/mbgl/style/layer.hpp
include/mbgl/style/layer_type.hpp
@@ -401,6 +414,7 @@ set(MBGL_CORE_FILES
include/mbgl/style/conversion/geojson.hpp
include/mbgl/style/conversion/geojson_options.hpp
include/mbgl/style/conversion/get_json_type.hpp
+ include/mbgl/style/conversion/heatmap_color_property_value.hpp
include/mbgl/style/conversion/layer.hpp
include/mbgl/style/conversion/light.hpp
include/mbgl/style/conversion/position.hpp
@@ -495,6 +509,7 @@ set(MBGL_CORE_FILES
include/mbgl/style/layers/custom_layer.hpp
include/mbgl/style/layers/fill_extrusion_layer.hpp
include/mbgl/style/layers/fill_layer.hpp
+ include/mbgl/style/layers/heatmap_layer.hpp
include/mbgl/style/layers/hillshade_layer.hpp
include/mbgl/style/layers/line_layer.hpp
include/mbgl/style/layers/raster_layer.hpp
@@ -522,6 +537,11 @@ set(MBGL_CORE_FILES
src/mbgl/style/layers/fill_layer_impl.hpp
src/mbgl/style/layers/fill_layer_properties.cpp
src/mbgl/style/layers/fill_layer_properties.hpp
+ src/mbgl/style/layers/heatmap_layer.cpp
+ src/mbgl/style/layers/heatmap_layer_impl.cpp
+ src/mbgl/style/layers/heatmap_layer_impl.hpp
+ src/mbgl/style/layers/heatmap_layer_properties.cpp
+ src/mbgl/style/layers/heatmap_layer_properties.hpp
src/mbgl/style/layers/hillshade_layer.cpp
src/mbgl/style/layers/hillshade_layer_impl.cpp
src/mbgl/style/layers/hillshade_layer_impl.hpp
diff --git a/cmake/test-files.cmake b/cmake/test-files.cmake
index 790198a55e..2aadfa8a7f 100644
--- a/cmake/test-files.cmake
+++ b/cmake/test-files.cmake
@@ -104,6 +104,7 @@ set(MBGL_TEST_FILES
test/src/mbgl/test/stub_file_source.hpp
test/src/mbgl/test/stub_geometry_tile_feature.hpp
test/src/mbgl/test/stub_layer_observer.hpp
+ test/src/mbgl/test/stub_map_observer.hpp
test/src/mbgl/test/stub_render_source_observer.hpp
test/src/mbgl/test/stub_style_observer.hpp
test/src/mbgl/test/stub_tile_observer.hpp
diff --git a/include/mbgl/renderer/renderer.hpp b/include/mbgl/renderer/renderer.hpp
index db28ee92fc..798928087a 100644
--- a/include/mbgl/renderer/renderer.hpp
+++ b/include/mbgl/renderer/renderer.hpp
@@ -48,7 +48,7 @@ public:
void dumpDebugLogs();
// Memory
- void onLowMemory();
+ void reduceMemoryUse();
private:
class Impl;
diff --git a/src/mbgl/renderer/renderer_observer.hpp b/include/mbgl/renderer/renderer_observer.hpp
index 551b5c803e..551b5c803e 100644
--- a/src/mbgl/renderer/renderer_observer.hpp
+++ b/include/mbgl/renderer/renderer_observer.hpp
diff --git a/include/mbgl/style/conversion/custom_geometry_source_options.hpp b/include/mbgl/style/conversion/custom_geometry_source_options.hpp
index 73b141e799..dedecd1aa4 100644
--- a/include/mbgl/style/conversion/custom_geometry_source_options.hpp
+++ b/include/mbgl/style/conversion/custom_geometry_source_options.hpp
@@ -54,6 +54,26 @@ struct Converter<CustomGeometrySource::Options> {
}
}
+ const auto wrapValue = objectMember(value, "wrap");
+ if (wrapValue) {
+ if (toBool(*wrapValue)) {
+ options.tileOptions.wrap = static_cast<bool>(*toBool(*wrapValue));
+ } else {
+ error = { "CustomGeometrySource TileOptions wrap value must be a boolean" };
+ return {};
+ }
+ }
+
+ const auto clipValue = objectMember(value, "clip");
+ if (clipValue) {
+ if (toBool(*clipValue)) {
+ options.tileOptions.clip = static_cast<double>(*toBool(*clipValue));
+ } else {
+ error = { "CustomGeometrySource TileOptiosn clip value must be a boolean" };
+ return {};
+ }
+ }
+
return { options };
}
diff --git a/include/mbgl/style/conversion/heatmap_color_property_value.hpp b/include/mbgl/style/conversion/heatmap_color_property_value.hpp
new file mode 100644
index 0000000000..e3689c524c
--- /dev/null
+++ b/include/mbgl/style/conversion/heatmap_color_property_value.hpp
@@ -0,0 +1,46 @@
+#pragma once
+
+#include <mbgl/style/heatmap_color_property_value.hpp>
+#include <mbgl/style/conversion.hpp>
+#include <mbgl/style/conversion/constant.hpp>
+#include <mbgl/style/conversion/function.hpp>
+#include <mbgl/style/conversion/expression.hpp>
+#include <mbgl/style/expression/value.hpp>
+#include <mbgl/style/expression/is_constant.hpp>
+#include <mbgl/style/expression/is_expression.hpp>
+#include <mbgl/style/expression/find_zoom_curve.hpp>
+
+namespace mbgl {
+namespace style {
+namespace conversion {
+
+template <>
+struct Converter<HeatmapColorPropertyValue> {
+ optional<HeatmapColorPropertyValue> operator()(const Convertible& value, Error& error) const {
+ if (isUndefined(value)) {
+ return HeatmapColorPropertyValue();
+ } else if (isExpression(value)) {
+ optional<std::unique_ptr<Expression>> expression = convert<std::unique_ptr<Expression>>(value, error, expression::type::Color);
+ if (!expression) {
+ return {};
+ }
+ assert(*expression);
+ if (!isFeatureConstant(**expression)) {
+ error = { "property expressions not supported" };
+ return {};
+ }
+ if (!isZoomConstant(**expression)) {
+ error = { "zoom expressions not supported" };
+ return {};
+ }
+ return {HeatmapColorPropertyValue(std::move(*expression))};
+ } else {
+ error = { "heatmap-color must be an expression" };
+ return {};
+ }
+ }
+};
+
+} // namespace conversion
+} // namespace style
+} // namespace mbgl
diff --git a/include/mbgl/style/expression/array_assertion.hpp b/include/mbgl/style/expression/array_assertion.hpp
index 7f36f8aac2..af153611ff 100644
--- a/include/mbgl/style/expression/array_assertion.hpp
+++ b/include/mbgl/style/expression/array_assertion.hpp
@@ -33,6 +33,9 @@ public:
std::vector<optional<Value>> possibleOutputs() const override {
return input->possibleOutputs();
}
+
+ mbgl::Value serialize() const override;
+ std::string getOperator() const override { return "array"; }
private:
std::unique_ptr<Expression> input;
diff --git a/include/mbgl/style/expression/assertion.hpp b/include/mbgl/style/expression/assertion.hpp
index 43ea73f2ba..d1e919b10f 100644
--- a/include/mbgl/style/expression/assertion.hpp
+++ b/include/mbgl/style/expression/assertion.hpp
@@ -26,6 +26,8 @@ public:
bool operator==(const Expression& e) const override;
std::vector<optional<Value>> possibleOutputs() const override;
+
+ std::string getOperator() const override;
private:
std::vector<std::unique_ptr<Expression>> inputs;
diff --git a/include/mbgl/style/expression/at.hpp b/include/mbgl/style/expression/at.hpp
index 27fccc761f..1e6f1c7dd2 100644
--- a/include/mbgl/style/expression/at.hpp
+++ b/include/mbgl/style/expression/at.hpp
@@ -31,6 +31,8 @@ public:
std::vector<optional<Value>> possibleOutputs() const override {
return { nullopt };
}
+
+ std::string getOperator() const override { return "at"; }
private:
std::unique_ptr<Expression> index;
diff --git a/include/mbgl/style/expression/boolean_operator.hpp b/include/mbgl/style/expression/boolean_operator.hpp
index 115a096665..6d0f85756a 100644
--- a/include/mbgl/style/expression/boolean_operator.hpp
+++ b/include/mbgl/style/expression/boolean_operator.hpp
@@ -23,6 +23,7 @@ public:
bool operator==(const Expression& e) const override;
std::vector<optional<Value>> possibleOutputs() const override;
+ std::string getOperator() const override { return "any"; }
private:
std::vector<std::unique_ptr<Expression>> inputs;
};
@@ -41,6 +42,7 @@ public:
bool operator==(const Expression& e) const override;
std::vector<optional<Value>> possibleOutputs() const override;
+ std::string getOperator() const override { return "all"; }
private:
std::vector<std::unique_ptr<Expression>> inputs;
};
diff --git a/include/mbgl/style/expression/case.hpp b/include/mbgl/style/expression/case.hpp
index e61a55fc6d..667ca53712 100644
--- a/include/mbgl/style/expression/case.hpp
+++ b/include/mbgl/style/expression/case.hpp
@@ -28,6 +28,7 @@ public:
std::vector<optional<Value>> possibleOutputs() const override;
+ std::string getOperator() const override { return "case"; }
private:
std::vector<Branch> branches;
std::unique_ptr<Expression> otherwise;
diff --git a/include/mbgl/style/expression/coalesce.hpp b/include/mbgl/style/expression/coalesce.hpp
index 52d9498cbd..a858bef695 100644
--- a/include/mbgl/style/expression/coalesce.hpp
+++ b/include/mbgl/style/expression/coalesce.hpp
@@ -38,6 +38,7 @@ public:
return args.at(i).get();
}
+ std::string getOperator() const override { return "coalesce"; }
private:
Args args;
};
diff --git a/include/mbgl/style/expression/coercion.hpp b/include/mbgl/style/expression/coercion.hpp
index 40d2490186..d83bd6dfa7 100644
--- a/include/mbgl/style/expression/coercion.hpp
+++ b/include/mbgl/style/expression/coercion.hpp
@@ -28,6 +28,7 @@ public:
std::vector<optional<Value>> possibleOutputs() const override;
+ std::string getOperator() const override;
private:
EvaluationResult (*coerceSingleValue) (const Value& v);
std::vector<std::unique_ptr<Expression>> inputs;
diff --git a/include/mbgl/style/expression/compound_expression.hpp b/include/mbgl/style/expression/compound_expression.hpp
index 8b74027578..6baaae862f 100644
--- a/include/mbgl/style/expression/compound_expression.hpp
+++ b/include/mbgl/style/expression/compound_expression.hpp
@@ -40,14 +40,16 @@ namespace detail {
// each CompoundExpression definition's type::Type data from the type of its
// "evaluate" function.
struct SignatureBase {
- SignatureBase(type::Type result_, variant<std::vector<type::Type>, VarargsType> params_) :
+ SignatureBase(type::Type result_, variant<std::vector<type::Type>, VarargsType> params_, std::string name_) :
result(std::move(result_)),
- params(std::move(params_))
+ params(std::move(params_)),
+ name(std::move(name_))
{}
virtual ~SignatureBase() = default;
- virtual std::unique_ptr<Expression> makeExpression(const std::string& name, std::vector<std::unique_ptr<Expression>>) const = 0;
+ virtual std::unique_ptr<Expression> makeExpression(std::vector<std::unique_ptr<Expression>>) const = 0;
type::Type result;
variant<std::vector<type::Type>, VarargsType> params;
+ std::string name;
};
} // namespace detail
@@ -111,6 +113,10 @@ public:
}
return false;
}
+
+ std::string getOperator() const override {
+ return signature.name;
+ }
private:
Signature signature;
@@ -128,8 +134,7 @@ struct CompoundExpressionRegistry {
ParseResult parseCompoundExpression(const std::string name, const mbgl::style::conversion::Convertible& value, ParsingContext& ctx);
-ParseResult createCompoundExpression(const std::string& name,
- const CompoundExpressionRegistry::Definition& definition,
+ParseResult createCompoundExpression(const CompoundExpressionRegistry::Definition& definition,
std::vector<std::unique_ptr<Expression>> args,
ParsingContext& ctx);
diff --git a/include/mbgl/style/expression/equals.hpp b/include/mbgl/style/expression/equals.hpp
index 80550bd59d..54df890a68 100644
--- a/include/mbgl/style/expression/equals.hpp
+++ b/include/mbgl/style/expression/equals.hpp
@@ -21,6 +21,7 @@ public:
EvaluationResult evaluate(const EvaluationContext&) const override;
std::vector<optional<Value>> possibleOutputs() const override;
+ std::string getOperator() const override { return negate ? "!=" : "=="; }
private:
std::unique_ptr<Expression> lhs;
std::unique_ptr<Expression> rhs;
diff --git a/include/mbgl/style/expression/expression.hpp b/include/mbgl/style/expression/expression.hpp
index cf9fa0cb21..c41ac0b5f1 100644
--- a/include/mbgl/style/expression/expression.hpp
+++ b/include/mbgl/style/expression/expression.hpp
@@ -135,6 +135,17 @@ public:
* complete set of outputs is statically undecidable.
*/
virtual std::vector<optional<Value>> possibleOutputs() const = 0;
+
+ virtual mbgl::Value serialize() const {
+ std::vector<mbgl::Value> serialized;
+ serialized.emplace_back(getOperator());
+ eachChild([&](const Expression &child) {
+ serialized.emplace_back(child.serialize());
+ });
+ return serialized;
+ };
+
+ virtual std::string getOperator() const = 0;
protected:
template <typename T>
diff --git a/include/mbgl/style/expression/interpolate.hpp b/include/mbgl/style/expression/interpolate.hpp
index dbed74b4cd..cc744ac7b7 100644
--- a/include/mbgl/style/expression/interpolate.hpp
+++ b/include/mbgl/style/expression/interpolate.hpp
@@ -185,6 +185,9 @@ public:
}
return false;
}
+
+ mbgl::Value serialize() const override;
+ std::string getOperator() const override { return "interpolate"; }
};
} // namespace expression
diff --git a/include/mbgl/style/expression/let.hpp b/include/mbgl/style/expression/let.hpp
index 6829ded9b8..75d2adda62 100644
--- a/include/mbgl/style/expression/let.hpp
+++ b/include/mbgl/style/expression/let.hpp
@@ -39,6 +39,8 @@ public:
return result.get();
}
+ mbgl::Value serialize() const override;
+ std::string getOperator() const override { return "let"; }
private:
Bindings bindings;
std::unique_ptr<Expression> result;
@@ -66,6 +68,8 @@ public:
std::vector<optional<Value>> possibleOutputs() const override;
+ mbgl::Value serialize() const override;
+ std::string getOperator() const override { return "var"; }
private:
std::string name;
std::shared_ptr<Expression> value;
diff --git a/include/mbgl/style/expression/literal.hpp b/include/mbgl/style/expression/literal.hpp
index 82983d78af..d854b419f4 100644
--- a/include/mbgl/style/expression/literal.hpp
+++ b/include/mbgl/style/expression/literal.hpp
@@ -12,8 +12,16 @@ namespace expression {
class Literal : public Expression {
public:
- Literal(Value value_) : Expression(typeOf(value_)), value(value_) {}
- Literal(type::Array type_, std::vector<Value> value_) : Expression(type_), value(value_) {}
+ Literal(Value value_)
+ : Expression(typeOf(value_))
+ , value(value_)
+ {}
+
+ Literal(type::Array type_, std::vector<Value> value_)
+ : Expression(type_)
+ , value(value_)
+ {}
+
EvaluationResult evaluate(const EvaluationContext&) const override {
return value;
}
@@ -33,6 +41,8 @@ public:
return {{ value }};
}
+ mbgl::Value serialize() const override;
+ std::string getOperator() const override { return "literal"; }
private:
Value value;
};
diff --git a/include/mbgl/style/expression/match.hpp b/include/mbgl/style/expression/match.hpp
index 682d784b0f..3775e38067 100644
--- a/include/mbgl/style/expression/match.hpp
+++ b/include/mbgl/style/expression/match.hpp
@@ -32,7 +32,9 @@ public:
bool operator==(const Expression& e) const override;
std::vector<optional<Value>> possibleOutputs() const override;
-
+
+ mbgl::Value serialize() const override;
+ std::string getOperator() const override { return "match"; }
private:
std::unique_ptr<Expression> input;
Branches branches;
diff --git a/include/mbgl/style/expression/step.hpp b/include/mbgl/style/expression/step.hpp
index 6bf42e20f1..2f9524a53c 100644
--- a/include/mbgl/style/expression/step.hpp
+++ b/include/mbgl/style/expression/step.hpp
@@ -38,6 +38,8 @@ public:
static ParseResult parse(const mbgl::style::conversion::Convertible& value, ParsingContext& ctx);
+ mbgl::Value serialize() const override;
+ std::string getOperator() const override { return "step"; }
private:
const std::unique_ptr<Expression> input;
const std::map<double, std::unique_ptr<Expression>> stops;
diff --git a/include/mbgl/style/expression/value.hpp b/include/mbgl/style/expression/value.hpp
index be5be64752..7839ff2ca7 100644
--- a/include/mbgl/style/expression/value.hpp
+++ b/include/mbgl/style/expression/value.hpp
@@ -110,6 +110,7 @@ struct ValueConverter<float> {
template<>
struct ValueConverter<mbgl::Value> {
static Value toExpressionValue(const mbgl::Value& value);
+ static mbgl::Value fromExpressionValue(const Value& value);
};
template <typename T, std::size_t N>
diff --git a/include/mbgl/style/function/convert.hpp b/include/mbgl/style/function/convert.hpp
index 8e544d3ad5..401a81d52e 100644
--- a/include/mbgl/style/function/convert.hpp
+++ b/include/mbgl/style/function/convert.hpp
@@ -49,6 +49,7 @@ public:
return {};
}
+ std::string getOperator() const override { return "error"; }
private:
std::string message;
};
diff --git a/include/mbgl/style/heatmap_color_property_value.hpp b/include/mbgl/style/heatmap_color_property_value.hpp
new file mode 100644
index 0000000000..130639c6e2
--- /dev/null
+++ b/include/mbgl/style/heatmap_color_property_value.hpp
@@ -0,0 +1,49 @@
+#pragma once
+
+#include <mbgl/util/variant.hpp>
+#include <mbgl/style/undefined.hpp>
+#include <mbgl/style/function/camera_function.hpp>
+
+namespace mbgl {
+namespace style {
+
+/*
+ * Special-case implementation of (a subset of) the PropertyValue<T> interface
+ * used for building the HeatmapColor paint property traits class.
+ */
+class HeatmapColorPropertyValue {
+private:
+ std::shared_ptr<expression::Expression> value;
+
+ friend bool operator==(const HeatmapColorPropertyValue& lhs, const HeatmapColorPropertyValue& rhs) {
+ return (lhs.isUndefined() && rhs.isUndefined()) || (lhs.value && rhs.value && *(lhs.value) == *(rhs.value));
+ }
+
+ friend bool operator!=(const HeatmapColorPropertyValue& lhs, const HeatmapColorPropertyValue& rhs) {
+ return !(lhs == rhs);
+ }
+
+public:
+ HeatmapColorPropertyValue() : value(nullptr) {}
+ HeatmapColorPropertyValue(std::shared_ptr<expression::Expression> value_) : value(std::move(value_)) {}
+
+ bool isUndefined() const { return value.get() == nullptr; }
+
+ // noop, needed for batch evaluation of paint property values to compile
+ template <typename Evaluator>
+ Color evaluate(const Evaluator&, TimePoint = {}) const { return {}; }
+
+ Color evaluate(double heatmapDensity) const {
+ const auto result = value->evaluate(expression::EvaluationContext({}, nullptr, {heatmapDensity}));
+ return *expression::fromExpressionValue<Color>(*result);
+ }
+
+ bool isDataDriven() const { return false; }
+ bool hasDataDrivenPropertyDifference(const HeatmapColorPropertyValue&) const { return false; }
+
+ const expression::Expression& getExpression() const { return *value; }
+};
+
+
+} // namespace style
+} // namespace mbgl
diff --git a/include/mbgl/style/layer.hpp b/include/mbgl/style/layer.hpp
index 8a5a4236b3..12494f5387 100644
--- a/include/mbgl/style/layer.hpp
+++ b/include/mbgl/style/layer.hpp
@@ -23,6 +23,7 @@ class HillshadeLayer;
class BackgroundLayer;
class CustomLayer;
class FillExtrusionLayer;
+class HeatmapLayer;
class LayerObserver;
/**
@@ -93,6 +94,8 @@ public:
return std::forward<V>(visitor)(*as<CustomLayer>());
case LayerType::FillExtrusion:
return std::forward<V>(visitor)(*as<FillExtrusionLayer>());
+ case LayerType::Heatmap:
+ return std::forward<V>(visitor)(*as<HeatmapLayer>());
}
diff --git a/include/mbgl/style/layer_type.hpp b/include/mbgl/style/layer_type.hpp
index 951757134b..0987ea4d0a 100644
--- a/include/mbgl/style/layer_type.hpp
+++ b/include/mbgl/style/layer_type.hpp
@@ -13,6 +13,7 @@ enum class LayerType {
Background,
Custom,
FillExtrusion,
+ Heatmap,
};
} // namespace style
diff --git a/include/mbgl/style/layers/heatmap_layer.hpp b/include/mbgl/style/layers/heatmap_layer.hpp
new file mode 100644
index 0000000000..33d927ad38
--- /dev/null
+++ b/include/mbgl/style/layers/heatmap_layer.hpp
@@ -0,0 +1,86 @@
+// This file is generated. Do not edit.
+
+#pragma once
+
+#include <mbgl/style/layer.hpp>
+#include <mbgl/style/filter.hpp>
+#include <mbgl/style/property_value.hpp>
+#include <mbgl/style/data_driven_property_value.hpp>
+#include <mbgl/style/heatmap_color_property_value.hpp>
+
+#include <mbgl/util/color.hpp>
+
+namespace mbgl {
+namespace style {
+
+class TransitionOptions;
+
+class HeatmapLayer : public Layer {
+public:
+ HeatmapLayer(const std::string& layerID, const std::string& sourceID);
+ ~HeatmapLayer() final;
+
+ // Source
+ const std::string& getSourceID() const;
+ const std::string& getSourceLayer() const;
+ void setSourceLayer(const std::string& sourceLayer);
+
+ void setFilter(const Filter&);
+ const Filter& getFilter() const;
+
+ // Visibility
+ void setVisibility(VisibilityType) final;
+
+ // Zoom range
+ void setMinZoom(float) final;
+ void setMaxZoom(float) final;
+
+ // Paint properties
+
+ static DataDrivenPropertyValue<float> getDefaultHeatmapRadius();
+ DataDrivenPropertyValue<float> getHeatmapRadius() const;
+ void setHeatmapRadius(DataDrivenPropertyValue<float>);
+ void setHeatmapRadiusTransition(const TransitionOptions&);
+ TransitionOptions getHeatmapRadiusTransition() const;
+
+ static DataDrivenPropertyValue<float> getDefaultHeatmapWeight();
+ DataDrivenPropertyValue<float> getHeatmapWeight() const;
+ void setHeatmapWeight(DataDrivenPropertyValue<float>);
+ void setHeatmapWeightTransition(const TransitionOptions&);
+ TransitionOptions getHeatmapWeightTransition() const;
+
+ static PropertyValue<float> getDefaultHeatmapIntensity();
+ PropertyValue<float> getHeatmapIntensity() const;
+ void setHeatmapIntensity(PropertyValue<float>);
+ void setHeatmapIntensityTransition(const TransitionOptions&);
+ TransitionOptions getHeatmapIntensityTransition() const;
+
+ static HeatmapColorPropertyValue getDefaultHeatmapColor();
+ HeatmapColorPropertyValue getHeatmapColor() const;
+ void setHeatmapColor(HeatmapColorPropertyValue);
+ void setHeatmapColorTransition(const TransitionOptions&);
+ TransitionOptions getHeatmapColorTransition() const;
+
+ static PropertyValue<float> getDefaultHeatmapOpacity();
+ PropertyValue<float> getHeatmapOpacity() const;
+ void setHeatmapOpacity(PropertyValue<float>);
+ void setHeatmapOpacityTransition(const TransitionOptions&);
+ TransitionOptions getHeatmapOpacityTransition() const;
+
+ // Private implementation
+
+ class Impl;
+ const Impl& impl() const;
+
+ Mutable<Impl> mutableImpl() const;
+ HeatmapLayer(Immutable<Impl>);
+ std::unique_ptr<Layer> cloneRef(const std::string& id) const final;
+};
+
+template <>
+inline bool Layer::is<HeatmapLayer>() const {
+ return getType() == LayerType::Heatmap;
+}
+
+} // namespace style
+} // namespace mbgl
diff --git a/include/mbgl/style/layers/layer.hpp.ejs b/include/mbgl/style/layers/layer.hpp.ejs
index 265dd57e1f..6d40405ccd 100644
--- a/include/mbgl/style/layers/layer.hpp.ejs
+++ b/include/mbgl/style/layers/layer.hpp.ejs
@@ -11,6 +11,9 @@
#include <mbgl/style/filter.hpp>
#include <mbgl/style/property_value.hpp>
#include <mbgl/style/data_driven_property_value.hpp>
+<% if (type === 'heatmap') { -%>
+#include <mbgl/style/heatmap_color_property_value.hpp>
+<% } -%>
#include <mbgl/util/color.hpp>
diff --git a/include/mbgl/style/sources/custom_geometry_source.hpp b/include/mbgl/style/sources/custom_geometry_source.hpp
index a0b990b44b..9daeeb3819 100644
--- a/include/mbgl/style/sources/custom_geometry_source.hpp
+++ b/include/mbgl/style/sources/custom_geometry_source.hpp
@@ -25,6 +25,8 @@ public:
double tolerance = 0.375;
uint16_t tileSize = util::tileSize;
uint16_t buffer = 128;
+ bool clip = false;
+ bool wrap = false;
};
struct Options {
diff --git a/include/mbgl/util/run_loop.hpp b/include/mbgl/util/run_loop.hpp
index acbea80273..381e3ae213 100644
--- a/include/mbgl/util/run_loop.hpp
+++ b/include/mbgl/util/run_loop.hpp
@@ -26,6 +26,11 @@ public:
New,
};
+ enum class Priority : bool {
+ Default = false,
+ High = true,
+ };
+
enum class Event : uint8_t {
None = 0,
Read = 1,
@@ -49,9 +54,14 @@ public:
// Invoke fn(args...) on this RunLoop.
template <class Fn, class... Args>
+ void invoke(Priority priority, Fn&& fn, Args&&... args) {
+ push(priority, WorkTask::make(std::forward<Fn>(fn), std::forward<Args>(args)...));
+ }
+
+ // Invoke fn(args...) on this RunLoop.
+ template <class Fn, class... Args>
void invoke(Fn&& fn, Args&&... args) {
- std::shared_ptr<WorkTask> task = WorkTask::make(std::forward<Fn>(fn), std::forward<Args>(args)...);
- push(task);
+ invoke(Priority::Default, std::forward<Fn>(fn), std::forward<Args>(args)...);
}
// Post the cancellable work fn(args...) to this RunLoop.
@@ -59,7 +69,7 @@ public:
std::unique_ptr<AsyncRequest>
invokeCancellable(Fn&& fn, Args&&... args) {
std::shared_ptr<WorkTask> task = WorkTask::make(std::forward<Fn>(fn), std::forward<Args>(args)...);
- push(task);
+ push(Priority::Default, task);
return std::make_unique<WorkRequest>(task);
}
@@ -76,24 +86,42 @@ private:
using Queue = std::queue<std::shared_ptr<WorkTask>>;
- void push(std::shared_ptr<WorkTask>);
+ // Wakes up the RunLoop so that it starts processing items in the queue.
+ void wake();
- void withMutex(std::function<void()>&& fn) {
+ // Adds a WorkTask to the queue, and wakes it up.
+ void push(Priority priority, std::shared_ptr<WorkTask> task) {
std::lock_guard<std::mutex> lock(mutex);
- fn();
+ if (priority == Priority::High) {
+ highPriorityQueue.emplace(std::move(task));
+ } else {
+ defaultQueue.emplace(std::move(task));
+ }
+ wake();
}
void process() {
- Queue queue_;
- withMutex([&] { queue_.swap(queue); });
-
- while (!queue_.empty()) {
- (*(queue_.front()))();
- queue_.pop();
+ std::shared_ptr<WorkTask> task;
+ std::unique_lock<std::mutex> lock(mutex);
+ while (true) {
+ if (!highPriorityQueue.empty()) {
+ task = std::move(highPriorityQueue.front());
+ highPriorityQueue.pop();
+ } else if (!defaultQueue.empty()) {
+ task = std::move(defaultQueue.front());
+ defaultQueue.pop();
+ } else {
+ break;
+ }
+ lock.unlock();
+ (*task)();
+ task.reset();
+ lock.lock();
}
}
- Queue queue;
+ Queue defaultQueue;
+ Queue highPriorityQueue;
std::mutex mutex;
std::unique_ptr<Impl> impl;
diff --git a/include/mbgl/util/thread.hpp b/include/mbgl/util/thread.hpp
index 672eebf6db..74e722b02d 100644
--- a/include/mbgl/util/thread.hpp
+++ b/include/mbgl/util/thread.hpp
@@ -103,7 +103,7 @@ public:
auto pausing = paused->get_future();
- loop->invoke([this] {
+ loop->invoke(RunLoop::Priority::High, [this] {
auto resuming = resumed->get_future();
paused->set_value();
resuming.get();
@@ -128,26 +128,9 @@ private:
MBGL_STORE_THREAD(tid);
void schedule(std::weak_ptr<Mailbox> mailbox) override {
- {
- std::lock_guard<std::mutex> lock(mutex);
- queue.push(mailbox);
- }
-
- loop->invoke([this] { receive(); });
- }
-
- void receive() {
- std::unique_lock<std::mutex> lock(mutex);
-
- auto mailbox = queue.front();
- queue.pop();
- lock.unlock();
-
- Mailbox::maybeReceive(mailbox);
+ loop->schedule(mailbox);
}
- std::mutex mutex;
- std::queue<std::weak_ptr<Mailbox>> queue;
std::thread thread;
std::unique_ptr<Actor<Object>> object;
diff --git a/include/mbgl/util/tileset.hpp b/include/mbgl/util/tileset.hpp
index 1b7b8f0f75..ed2b907647 100644
--- a/include/mbgl/util/tileset.hpp
+++ b/include/mbgl/util/tileset.hpp
@@ -14,22 +14,28 @@ namespace mbgl {
class Tileset {
public:
enum class Scheme : bool { XYZ, TMS };
+ enum class DEMEncoding : bool { Mapbox, Terrarium };
std::vector<std::string> tiles;
Range<uint8_t> zoomRange;
std::string attribution;
Scheme scheme;
+ // DEMEncoding is not supported by the TileJSON spec
+ DEMEncoding encoding;
optional<LatLngBounds> bounds;
+
Tileset(std::vector<std::string> tiles_ = std::vector<std::string>(),
Range<uint8_t> zoomRange_ = { 0, util::DEFAULT_MAX_ZOOM },
std::string attribution_ = {},
- Scheme scheme_ = Scheme::XYZ)
+ Scheme scheme_ = Scheme::XYZ,
+ DEMEncoding encoding_ = DEMEncoding::Mapbox)
: tiles(std::move(tiles_)),
zoomRange(std::move(zoomRange_)),
attribution(std::move(attribution_)),
scheme(scheme_),
- bounds() {}
+ encoding(encoding_),
+ bounds() {};
// TileJSON also includes center and zoom but they are not used by mbgl.
diff --git a/mapbox-gl-js b/mapbox-gl-js
-Subproject de365184e13c08fb42bbd93a08abfc859829499
+Subproject 8a19f6079933817fd73eed71159130b8ac53a00
diff --git a/package.json b/package.json
index 4ff4b32735..977cd2b09c 100644
--- a/package.json
+++ b/package.json
@@ -13,7 +13,7 @@
},
"license": "BSD-2-Clause",
"dependencies": {
- "nan": "^2.6.2",
+ "nan": "~2.8",
"node-pre-gyp": "^0.6.37",
"npm-run-all": "^4.0.2"
},
diff --git a/platform/android/CHANGELOG.md b/platform/android/CHANGELOG.md
index fa212b86ad..60d3dc6b70 100644
--- a/platform/android/CHANGELOG.md
+++ b/platform/android/CHANGELOG.md
@@ -2,6 +2,52 @@
Mapbox welcomes participation and contributions from everyone. If you'd like to do so please see the [`Contributing Guide`](https://github.com/mapbox/mapbox-gl-native/blob/master/CONTRIBUTING.md) first to get started.
+## 6.0.0-beta.3 - March 2, 2018
+ - Added missing local reference deletes [#11243](https://github.com/mapbox/mapbox-gl-native/pull/11243), [#11272](https://github.com/mapbox/mapbox-gl-native/pull/11272)
+ - Remove obsolete camera api [#11201](https://github.com/mapbox/mapbox-gl-native/pull/11201)
+ - Fix UTF-8 encoding, add missing package-info.java files [#11261](https://github.com/mapbox/mapbox-gl-native/pull/11261)
+ - Rework expression api [#11210](https://github.com/mapbox/mapbox-gl-native/pull/11210)
+ - LatLngBounds fixes [#11333](https://github.com/mapbox/mapbox-gl-native/pull/11333), [#11307](https://github.com/mapbox/mapbox-gl-native/pull/11307), [#11308](https://github.com/mapbox/mapbox-gl-native/pull/11308), [#11309](https://github.com/mapbox/mapbox-gl-native/pull/11309), [#11226](https://github.com/mapbox/mapbox-gl-native/pull/11226)
+ - New gestures library [#11221](https://github.com/mapbox/mapbox-gl-native/pull/11221)
+ - Expose ImageSource coordinates setter [#11262](https://github.com/mapbox/mapbox-gl-native/pull/11262)
+ - Add heatmap color property [#11220](https://github.com/mapbox/mapbox-gl-native/pull/11220)
+ - Add support for mapzen terrarium raster-dem encoding [#11339](https://github.com/mapbox/mapbox-gl-native/pull/11339)
+
+## 5.5.0 - March 1, 2018
+ - TileJSON Bounds allows values inclusive of world extents [#11178](https://github.com/mapbox/mapbox-gl-native/pull/11178)
+ - LatLngBounds returned by VisibleRegion when map is rotated [#11226](https://github.com/mapbox/mapbox-gl-native/pull/11226)
+ - Custom Layer fixes & black list VAO on mali t720 [#11239](https://github.com/mapbox/mapbox-gl-native/pull/11239)
+ - Check if Activity isn't finishing before showing dialog [#11244](https://github.com/mapbox/mapbox-gl-native/pull/11244)
+ - Decouple MapPadding from overlain views [#11258](https://github.com/mapbox/mapbox-gl-native/pull/11258)
+ - Don't disable zoom button controller zooming with gesture disabled zoom [#11259](https://github.com/mapbox/mapbox-gl-native/pull/11259)
+ - Expose ImageSource coordinates setter [#11262](https://github.com/mapbox/mapbox-gl-native/pull/11262)
+ - Add missing DeleteLocalRefs [#11272](https://github.com/mapbox/mapbox-gl-native/pull/11272)
+ - Continue loading style even if we mutate it [#11294](https://github.com/mapbox/mapbox-gl-native/pull/11294)
+ - Update telemetry version for OkHttp [#11338](https://github.com/mapbox/mapbox-gl-native/pull/11338)
+
+## 6.0.0-beta.2 - February 13, 2018
+ - Deprecate LocationEngine [#11185](https://github.com/mapbox/mapbox-gl-native/pull/11185)
+ - Remove LOST from SDK [11186](https://github.com/mapbox/mapbox-gl-native/pull/11186)
+ - Transparent surface configuration on TextureView [#11065](https://github.com/mapbox/mapbox-gl-native/pull/11065)
+ - Constrained setLatLng documentation, expose setLatLngZoom method [#11184](https://github.com/mapbox/mapbox-gl-native/pull/11184)
+ - Integration of new events library [#10999](https://github.com/mapbox/mapbox-gl-native/pull/10999)
+ - AddImage performance improvement [#11111](https://github.com/mapbox/mapbox-gl-native/pull/11111)
+ - Migrate MAS to 3.0.0, refactor GeoJson integration [#11149](https://github.com/mapbox/mapbox-gl-native/pull/11149)
+ - Remove @jar and @aar dependency suffixes [#11161](https://github.com/mapbox/mapbox-gl-native/pull/11161)
+
+## 5.4.1 - February 9, 2018
+ - Don't recreate TextureView surface as part of view resizing, solves OOM crashes [#11148](https://github.com/mapbox/mapbox-gl-native/pull/11148)
+ - Don't invoke OnLowMemory before map is ready, solves startup crash on low memory devices [#11109](https://github.com/mapbox/mapbox-gl-native/pull/11109)
+ - Programmatically create GLSurfaceView, solves fragment bug [#11124](https://github.com/mapbox/mapbox-gl-native/pull/11124)
+ - Proguard config for optional location provider, solves obfuscation warnings [#11127](https://github.com/mapbox/mapbox-gl-native/pull/11127)
+ - MapView weak reference in global layout listener, solves memory leak [#11128](https://github.com/mapbox/mapbox-gl-native/pull/11128)
+
+## 5.4.0 - January 30, 2018
+ - Blacklist Adreno 2xx GPU for VAO support [#11047](https://github.com/mapbox/mapbox-gl-native/pull/11047)
+ - Bearing tracking mode GPS_NORTH_FACING [#11095](https://github.com/mapbox/mapbox-gl-native/pull/11095)
+ - Disable logging for missing location permissions when location is disabled [#11084](https://github.com/mapbox/mapbox-gl-native/pull/11084)
+ - Create offline handler using the main thread looper [#11021](https://github.com/mapbox/mapbox-gl-native/pull/11021)
+
## 6.0.0-beta.1 - January 26, 2018
- Binding integration for expressions [#10654](https://github.com/mapbox/mapbox-gl-native/pull/10654)
- CustomGeometrySource [#9983](https://github.com/mapbox/mapbox-gl-native/pull/9983)
diff --git a/platform/android/DISTRIBUTE.md b/platform/android/DISTRIBUTE.md
index 648f26ce25..1c61748322 100644
--- a/platform/android/DISTRIBUTE.md
+++ b/platform/android/DISTRIBUTE.md
@@ -13,7 +13,7 @@ BUILDTYPE=Debug or BUILDTYPE=Release
##### Creating an Android Archive file that supports all ABIs
```sh
-BUILDTYPE=RELEASE make apackage
+BUILDTYPE=Release make apackage
```
This will build native libraries to support following ABIs:
diff --git a/platform/android/MapboxGLAndroidSDK/.gitignore b/platform/android/MapboxGLAndroidSDK/.gitignore
deleted file mode 100644
index cec211fe81..0000000000
--- a/platform/android/MapboxGLAndroidSDK/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-lint-baseline.xml
-lint/lint-baseline-local.xml \ No newline at end of file
diff --git a/platform/android/MapboxGLAndroidSDK/build.gradle b/platform/android/MapboxGLAndroidSDK/build.gradle
index 0900f700c8..9063321648 100644
--- a/platform/android/MapboxGLAndroidSDK/build.gradle
+++ b/platform/android/MapboxGLAndroidSDK/build.gradle
@@ -3,6 +3,7 @@ apply plugin: 'com.android.library'
dependencies {
api dependenciesList.mapboxAndroidTelemetry
api dependenciesList.mapboxJavaGeoJSON
+ api dependenciesList.mapboxAndroidGestures
implementation dependenciesList.supportAnnotations
implementation dependenciesList.supportFragmentV4
implementation dependenciesList.timber
@@ -25,6 +26,8 @@ android {
minSdkVersion androidVersions.minSdkVersion
targetSdkVersion androidVersions.targetSdkVersion
buildConfigField "String", "GIT_REVISION_SHORT", String.format("\"%s\"", getGitRevision())
+ buildConfigField "String", "MAPBOX_SDK_IDENTIFIER", String.format("\"%s\"", "mapbox-maps-android")
+ buildConfigField "String", "MAPBOX_SDK_VERSION", String.format("\"%s\"", project.VERSION_NAME)
buildConfigField "String", "MAPBOX_VERSION_STRING", String.format("\"Mapbox/%s\"", project.VERSION_NAME)
buildConfigField "String", "MAPBOX_EVENTS_USER_AGENT", String.format("\"MapboxEventsAndroid/%s\"", project.VERSION_NAME)
}
@@ -114,7 +117,6 @@ android {
lintOptions {
disable 'MissingTranslation', 'TypographyQuotes', 'ObsoleteLintCustomCheck', 'MissingPermission'
- baseline file("lint-baseline-local.xml")
checkAllWarnings true
warningsAsErrors false
}
@@ -148,4 +150,3 @@ apply from: "${rootDir}/gradle/gradle-javadoc.gradle"
apply from: "${rootDir}/gradle/gradle-publish.gradle"
apply from: "${rootDir}/gradle/gradle-checkstyle.gradle"
apply from: "${rootDir}/gradle/gradle-tests-staticblockremover.gradle"
-apply from: "${rootDir}/gradle/gradle-lint.gradle"
diff --git a/platform/android/MapboxGLAndroidSDK/lint-baseline-local.xml b/platform/android/MapboxGLAndroidSDK/lint-baseline-local.xml
deleted file mode 100644
index 0a76f53505..0000000000
--- a/platform/android/MapboxGLAndroidSDK/lint-baseline-local.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="4" by="lint 2.3.1">
-
- <issue
- id="MissingTranslation"
- message="&quot;`mapbox_attributionErrorNoBrowser`&quot; is not translated in &quot;ca&quot; (Catalan), &quot;es&quot; (Spanish), &quot;lt&quot; (Lithuanian), &quot;nl&quot; (Dutch), &quot;sv&quot; (Swedish), &quot;vi&quot; (Vietnamese)"
- errorLine1=" &lt;string name=&quot;mapbox_attributionErrorNoBrowser&quot;>No web browser installed on device, can\&apos;t open web page.&lt;/string>"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/res/values/strings.xml"
- line="13"
- column="13"/>
- </issue>
-
- <issue
- id="MissingTranslation"
- message="&quot;`mapbox_telemetrySettings`&quot; is not translated in &quot;ca&quot; (Catalan), &quot;es&quot; (Spanish), &quot;lt&quot; (Lithuanian), &quot;nl&quot; (Dutch), &quot;sv&quot; (Swedish), &quot;vi&quot; (Vietnamese)"
- errorLine1=" &lt;string name=&quot;mapbox_telemetrySettings&quot;>Telemetry Settings&lt;/string>"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/res/values/strings.xml"
- line="15"
- column="13"/>
- </issue>
-
- <issue
- id="TypographyQuotes"
- message="Replace straight quotes (&apos;&apos;) with directional quotes (‘’, &amp;#8216; and &amp;#8217;) ?"
- errorLine1=" &lt;string name=&quot;mapbox_attributionTelemetryMessage&quot;>Estàs ajudant a millorar els mapes d\&apos;OpenStreetMap i de Mapbox aportant dades d\&apos;ús anònimes.&lt;/string>"
- errorLine2=" ^">
- <location
- file="src/main/res/values-ca/strings.xml"
- line="9"
- column="55"/>
- </issue>
-
-</issues>
diff --git a/platform/android/MapboxGLAndroidSDK/lint/lint-baseline-ci.xml b/platform/android/MapboxGLAndroidSDK/lint/lint-baseline-ci.xml
deleted file mode 100644
index fd65c9f627..0000000000
--- a/platform/android/MapboxGLAndroidSDK/lint/lint-baseline-ci.xml
+++ /dev/null
@@ -1,44 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- REMEMBER! First you run Lint locally you'll need to move lint-baseline-local.xml.xml file
- generated into the lint folder and called it lint-baseline-local.xml
- If you remove any error when running Lint locally, you'll get a warning from the command
- line advising you to remove it from the baseline. If you remove it (remember to remove it
- from lint-baseline-local.xml file) you should remove it too from
- lint-baseline-ci.xml (THIS FILE) which is the only one included in the repo.
- Eventually, it'll be removed (when we remove all current lint errors included). -->
-<issues format="4" by="lint 2.3.1">
-
- <issue
- id="MissingTranslation"
- message="&quot;`mapbox_attributionErrorNoBrowser`&quot; is not translated in &quot;ca&quot; (Catalan), &quot;es&quot; (Spanish), &quot;lt&quot; (Lithuanian), &quot;nl&quot; (Dutch), &quot;sv&quot; (Swedish), &quot;vi&quot; (Vietnamese)"
- errorLine1=" &lt;string name=&quot;mapbox_attributionErrorNoBrowser&quot;>No web browser installed on device, can\&apos;t open web page.&lt;/string>"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/res/values/strings.xml"
- line="13"
- column="13"/>
- </issue>
-
- <issue
- id="MissingTranslation"
- message="&quot;`mapbox_telemetrySettings`&quot; is not translated in &quot;ca&quot; (Catalan), &quot;es&quot; (Spanish), &quot;lt&quot; (Lithuanian), &quot;nl&quot; (Dutch), &quot;sv&quot; (Swedish), &quot;vi&quot; (Vietnamese)"
- errorLine1=" &lt;string name=&quot;mapbox_telemetrySettings&quot;>Telemetry Settings&lt;/string>"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/res/values/strings.xml"
- line="15"
- column="13"/>
- </issue>
-
- <issue
- id="TypographyQuotes"
- message="Replace straight quotes (&apos;&apos;) with directional quotes (‘’, &amp;#8216; and &amp;#8217;) ?"
- errorLine1=" &lt;string name=&quot;mapbox_attributionTelemetryMessage&quot;>Estàs ajudant a millorar els mapes d\&apos;OpenStreetMap i de Mapbox aportant dades d\&apos;ús anònimes.&lt;/string>"
- errorLine2=" ^">
- <location
- file="src/main/res/values-ca/strings.xml"
- line="9"
- column="55"/>
- </issue>
-
-</issues>
diff --git a/platform/android/MapboxGLAndroidSDK/proguard-rules.pro b/platform/android/MapboxGLAndroidSDK/proguard-rules.pro
index b5a1d82c81..3b8adac5a8 100644
--- a/platform/android/MapboxGLAndroidSDK/proguard-rules.pro
+++ b/platform/android/MapboxGLAndroidSDK/proguard-rules.pro
@@ -12,4 +12,7 @@
# config for okhttp 3.8.0, https://github.com/square/okhttp/pull/3354
-dontwarn okio.**
-dontwarn javax.annotation.Nullable
--dontwarn javax.annotation.ParametersAreNonnullByDefault \ No newline at end of file
+-dontwarn javax.annotation.ParametersAreNonnullByDefault
+
+# config for optional location provider https://github.com/mapbox/mapbox-gl-native/issues/10960
+-dontwarn com.mapzen.android.lost.api** \ No newline at end of file
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/BaseGestureDetector.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/BaseGestureDetector.java
deleted file mode 100644
index b7bcb925a1..0000000000
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/BaseGestureDetector.java
+++ /dev/null
@@ -1,160 +0,0 @@
-package com.almeros.android.multitouch.gesturedetectors;
-
-import android.content.Context;
-import android.view.MotionEvent;
-
-/**
- * @author Almer Thie (code.almeros.com) Copyright (c) 2013, Almer Thie
- * (code.almeros.com)
- * <p>
- * All rights reserved.
- * <p>
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * <p>
- * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * <p>
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
- * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
- * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
- * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
- * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
- * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- */
-public abstract class BaseGestureDetector {
- protected final Context context;
- protected boolean gestureInProgress;
-
- protected MotionEvent prevEvent;
- protected MotionEvent currEvent;
-
- protected float currPressure;
- protected float prevPressure;
- protected long timeDelta;
-
- /**
- * This value is the threshold ratio between the previous combined pressure
- * and the current combined pressure. When pressure decreases rapidly
- * between events the position values can often be imprecise, as it usually
- * indicates that the user is in the process of lifting a pointer off of the
- * device. This value was tuned experimentally.
- */
- protected static final float PRESSURE_THRESHOLD = 0.67f;
-
- public BaseGestureDetector(Context context) {
- this.context = context;
- }
-
- /**
- * All gesture detectors need to be called through this method to be able to
- * detect gestures. This method delegates work to handler methods
- * (handleStartProgressEvent, handleInProgressEvent) implemented in
- * extending classes.
- *
- * @param event MotionEvent
- * @return {@code true} as handled
- */
- public boolean onTouchEvent(MotionEvent event) {
- final int actionCode = event.getAction() & MotionEvent.ACTION_MASK;
- if (!gestureInProgress) {
- handleStartProgressEvent(actionCode, event);
- } else {
- handleInProgressEvent(actionCode, event);
- }
- return true;
- }
-
- /**
- * Called when the current event occurred when NO gesture is in progress
- * yet. The handling in this implementation may set the gesture in progress
- * (via gestureInProgress) or out of progress
- *
- * @param actionCode Action Code from MotionEvent
- * @param event MotionEvent
- */
- protected abstract void handleStartProgressEvent(int actionCode,
- MotionEvent event);
-
- /**
- * Called when the current event occurred when a gesture IS in progress. The
- * handling in this implementation may set the gesture out of progress (via
- * gestureInProgress).
- *
- * @param actionCode Action Code from MotionEvent
- * @param event MotionEvent
- */
- protected abstract void handleInProgressEvent(int actionCode,
- MotionEvent event);
-
- protected void updateStateByEvent(MotionEvent curr) {
- final MotionEvent prev = prevEvent;
-
- // Reset currEvent
- if (currEvent != null) {
- currEvent.recycle();
- currEvent = null;
- }
- currEvent = MotionEvent.obtain(curr);
-
- // Delta time
- timeDelta = curr.getEventTime() - prev.getEventTime();
-
- // Pressure
- currPressure = curr.getPressure(curr.getActionIndex());
- prevPressure = prev.getPressure(prev.getActionIndex());
- }
-
- protected void resetState() {
- if (prevEvent != null) {
- prevEvent.recycle();
- prevEvent = null;
- }
- if (currEvent != null) {
- currEvent.recycle();
- currEvent = null;
- }
- gestureInProgress = false;
- }
-
- /**
- * Returns {@code true} if a gesture is currently in progress.
- *
- * @return {@code true} if a gesture is currently in progress, {@code false}
- * otherwise.
- */
- public boolean isInProgress() {
- return gestureInProgress;
- }
-
- /**
- * Return the time difference in milliseconds between the previous accepted
- * GestureDetector event and the current GestureDetector event.
- *
- * @return Time difference since the last move event in milliseconds.
- */
- public long getTimeDelta() {
- return timeDelta;
- }
-
- /**
- * Return the event time of the current GestureDetector event being
- * processed.
- *
- * @return Current GestureDetector event time in milliseconds.
- */
- public long getEventTime() {
- return currEvent.getEventTime();
- }
-
-}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/MoveGestureDetector.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/MoveGestureDetector.java
deleted file mode 100644
index bc7dda6159..0000000000
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/MoveGestureDetector.java
+++ /dev/null
@@ -1,185 +0,0 @@
-package com.almeros.android.multitouch.gesturedetectors;
-
-import android.content.Context;
-import android.graphics.PointF;
-import android.view.MotionEvent;
-
-/**
- * @author Almer Thie (code.almeros.com) Copyright (c) 2013, Almer Thie
- * (code.almeros.com)
- * <p>
- * All rights reserved.
- * <p>
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * <p>
- * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * <p>
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
- * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
- * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
- * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
- * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
- * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- */
-public class MoveGestureDetector extends BaseGestureDetector {
-
- /**
- * Listener which must be implemented which is used by MoveGestureDetector
- * to perform callbacks to any implementing class which is registered to a
- * MoveGestureDetector via the constructor.
- *
- * @see MoveGestureDetector.SimpleOnMoveGestureListener
- */
- public interface OnMoveGestureListener {
- public boolean onMove(MoveGestureDetector detector);
-
- public boolean onMoveBegin(MoveGestureDetector detector);
-
- public void onMoveEnd(MoveGestureDetector detector);
- }
-
- /**
- * Helper class which may be extended and where the methods may be
- * implemented. This way it is not necessary to implement all methods of
- * OnMoveGestureListener.
- */
- public static class SimpleOnMoveGestureListener implements
- OnMoveGestureListener {
- public boolean onMove(MoveGestureDetector detector) {
- return false;
- }
-
- public boolean onMoveBegin(MoveGestureDetector detector) {
- return true;
- }
-
- public void onMoveEnd(MoveGestureDetector detector) {
- // Do nothing, overridden implementation may be used
- }
- }
-
- private static final PointF FOCUS_DELTA_ZERO = new PointF();
-
- private final OnMoveGestureListener listener;
-
- private PointF focusExternal = new PointF();
- private PointF focusDeltaExternal = new PointF();
-
- public MoveGestureDetector(Context context, OnMoveGestureListener listener) {
- super(context);
- this.listener = listener;
- }
-
- @Override
- protected void handleStartProgressEvent(int actionCode, MotionEvent event) {
- switch (actionCode) {
- case MotionEvent.ACTION_DOWN:
- resetState(); // In case we missed an UP/CANCEL event
-
- prevEvent = MotionEvent.obtain(event);
- timeDelta = 0;
-
- updateStateByEvent(event);
- break;
-
- case MotionEvent.ACTION_MOVE:
- gestureInProgress = listener.onMoveBegin(this);
- break;
- }
- }
-
- @Override
- protected void handleInProgressEvent(int actionCode, MotionEvent event) {
- switch (actionCode) {
- case MotionEvent.ACTION_UP:
- case MotionEvent.ACTION_CANCEL:
- listener.onMoveEnd(this);
- resetState();
- break;
-
- case MotionEvent.ACTION_MOVE:
- updateStateByEvent(event);
-
- // Only accept the event if our relative pressure is within
- // a certain limit. This can help filter shaky data as a
- // finger is lifted.
- if (currPressure / prevPressure > PRESSURE_THRESHOLD) {
- final boolean updatePrevious = listener.onMove(this);
- if (updatePrevious) {
- prevEvent.recycle();
- prevEvent = MotionEvent.obtain(event);
- }
- }
- break;
- }
- }
-
- protected void updateStateByEvent(MotionEvent curr) {
- super.updateStateByEvent(curr);
-
- final MotionEvent prev = prevEvent;
-
- // Focus intenal
- PointF currFocusInternal = determineFocalPoint(curr);
- PointF prevFocusInternal = determineFocalPoint(prev);
-
- // Focus external
- // - Prevent skipping of focus delta when a finger is added or removed
- boolean skipNextMoveEvent = prev.getPointerCount() != curr
- .getPointerCount();
- focusDeltaExternal = skipNextMoveEvent ? FOCUS_DELTA_ZERO
- : new PointF(currFocusInternal.x - prevFocusInternal.x,
- currFocusInternal.y - prevFocusInternal.y);
-
- // - Don't directly use mFocusInternal (or skipping will occur). Add
- // unskipped delta values to focusExternal instead.
- focusExternal.x += focusDeltaExternal.x;
- focusExternal.y += focusDeltaExternal.y;
- }
-
- /**
- * Determine (multi)finger focal point (a.k.a. center point between all
- * fingers)
- *
- * @param motionEvent a {@link MotionEvent} object.
- * @return PointF focal point
- */
- private PointF determineFocalPoint(MotionEvent motionEvent) {
- // Number of fingers on screen
- final int pCount = motionEvent.getPointerCount();
- float x = 0.0f;
- float y = 0.0f;
-
- for (int i = 0; i < pCount; i++) {
- x += motionEvent.getX(i);
- y += motionEvent.getY(i);
- }
-
- return new PointF(x / pCount, y / pCount);
- }
-
- public float getFocusX() {
- return focusExternal.x;
- }
-
- public float getFocusY() {
- return focusExternal.y;
- }
-
- public PointF getFocusDelta() {
- return focusDeltaExternal;
- }
-
-}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/RotateGestureDetector.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/RotateGestureDetector.java
deleted file mode 100644
index 8c111a68df..0000000000
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/RotateGestureDetector.java
+++ /dev/null
@@ -1,180 +0,0 @@
-package com.almeros.android.multitouch.gesturedetectors;
-
-import android.content.Context;
-import android.view.MotionEvent;
-
-/**
- * @author Almer Thie (code.almeros.com) Copyright (c) 2013, Almer Thie
- * (code.almeros.com)
- * <p>
- * All rights reserved.
- * <p>
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * <p>
- * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * <p>
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
- * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
- * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
- * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
- * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
- * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- */
-public class RotateGestureDetector extends TwoFingerGestureDetector {
-
- /**
- * Listener which must be implemented which is used by RotateGestureDetector
- * to perform callbacks to any implementing class which is registered to a
- * RotateGestureDetector via the constructor.
- *
- * @see RotateGestureDetector.SimpleOnRotateGestureListener
- */
- public interface OnRotateGestureListener {
- public boolean onRotate(RotateGestureDetector detector);
-
- public boolean onRotateBegin(RotateGestureDetector detector);
-
- public void onRotateEnd(RotateGestureDetector detector);
- }
-
- /**
- * Helper class which may be extended and where the methods may be
- * implemented. This way it is not necessary to implement all methods of
- * OnRotateGestureListener.
- */
- public static class SimpleOnRotateGestureListener implements
- OnRotateGestureListener {
- public boolean onRotate(RotateGestureDetector detector) {
- return false;
- }
-
- public boolean onRotateBegin(RotateGestureDetector detector) {
- return true;
- }
-
- public void onRotateEnd(RotateGestureDetector detector) {
- // Do nothing, overridden implementation may be used
- }
- }
-
- private final OnRotateGestureListener listener;
- private boolean sloppyGesture;
-
- public RotateGestureDetector(Context context,
- OnRotateGestureListener listener) {
- super(context);
- this.listener = listener;
- }
-
- @Override
- protected void handleStartProgressEvent(int actionCode, MotionEvent event) {
- switch (actionCode) {
- case MotionEvent.ACTION_POINTER_DOWN:
- // At least the second finger is on screen now
-
- resetState(); // In case we missed an UP/CANCEL event
- prevEvent = MotionEvent.obtain(event);
- timeDelta = 0;
-
- updateStateByEvent(event);
-
- // See if we have a sloppy gesture
- sloppyGesture = isSloppyGesture(event);
- if (!sloppyGesture) {
- // No, start gesture now
- gestureInProgress = listener.onRotateBegin(this);
- }
- break;
-
- case MotionEvent.ACTION_MOVE:
- if (!sloppyGesture) {
- break;
- }
-
- // See if we still have a sloppy gesture
- sloppyGesture = isSloppyGesture(event);
- if (!sloppyGesture) {
- // No, start normal gesture now
- gestureInProgress = listener.onRotateBegin(this);
- }
-
- break;
-
- case MotionEvent.ACTION_POINTER_UP:
- if (!sloppyGesture) {
- break;
- }
-
- break;
- }
- }
-
- @Override
- protected void handleInProgressEvent(int actionCode, MotionEvent event) {
- switch (actionCode) {
- case MotionEvent.ACTION_POINTER_UP:
- // Gesture ended but
- updateStateByEvent(event);
-
- if (!sloppyGesture) {
- listener.onRotateEnd(this);
- }
-
- resetState();
- break;
-
- case MotionEvent.ACTION_CANCEL:
- if (!sloppyGesture) {
- listener.onRotateEnd(this);
- }
-
- resetState();
- break;
-
- case MotionEvent.ACTION_MOVE:
- updateStateByEvent(event);
-
- // Only accept the event if our relative pressure is within
- // a certain limit. This can help filter shaky data as a
- // finger is lifted.
- if (currPressure / prevPressure > PRESSURE_THRESHOLD) {
- final boolean updatePrevious = listener.onRotate(this);
- if (updatePrevious) {
- prevEvent.recycle();
- prevEvent = MotionEvent.obtain(event);
- }
- }
- break;
- }
- }
-
- @Override
- protected void resetState() {
- super.resetState();
- sloppyGesture = false;
- }
-
- /**
- * Return the rotation difference from the previous rotate event to the
- * current event.
- *
- * @return The current rotation //difference in degrees.
- */
- public float getRotationDegreesDelta() {
- double diffRadians = Math.atan2(prevFingerDiffY, prevFingerDiffX)
- - Math.atan2(currFingerDiffY, currFingerDiffX);
- return (float) (diffRadians * 180.0 / Math.PI);
- }
-}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/ShoveGestureDetector.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/ShoveGestureDetector.java
deleted file mode 100644
index 9396578e48..0000000000
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/ShoveGestureDetector.java
+++ /dev/null
@@ -1,214 +0,0 @@
-package com.almeros.android.multitouch.gesturedetectors;
-
-import android.content.Context;
-import android.view.MotionEvent;
-
-/**
- * @author Robert Nordan (robert.nordan@norkart.no)
- * <p>
- * Copyright (c) 2013, Norkart AS
- * <p>
- * All rights reserved.
- * <p>
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * <p>
- * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * <p>
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
- * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
- * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
- * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
- * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
- * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- */
-public class ShoveGestureDetector extends TwoFingerGestureDetector {
-
- /**
- * Listener which must be implemented which is used by ShoveGestureDetector
- * to perform callbacks to any implementing class which is registered to a
- * ShoveGestureDetector via the constructor.
- *
- * @see ShoveGestureDetector.SimpleOnShoveGestureListener
- */
- public interface OnShoveGestureListener {
- public boolean onShove(ShoveGestureDetector detector);
-
- public boolean onShoveBegin(ShoveGestureDetector detector);
-
- public void onShoveEnd(ShoveGestureDetector detector);
- }
-
- /**
- * Helper class which may be extended and where the methods may be
- * implemented. This way it is not necessary to implement all methods of
- * OnShoveGestureListener.
- */
- public static class SimpleOnShoveGestureListener implements
- OnShoveGestureListener {
- public boolean onShove(ShoveGestureDetector detector) {
- return false;
- }
-
- public boolean onShoveBegin(ShoveGestureDetector detector) {
- return true;
- }
-
- public void onShoveEnd(ShoveGestureDetector detector) {
- // Do nothing, overridden implementation may be used
- }
- }
-
- private float prevAverageY;
- private float currAverageY;
-
- private final OnShoveGestureListener listener;
- private boolean sloppyGesture;
-
- public ShoveGestureDetector(Context context, OnShoveGestureListener listener) {
- super(context);
- this.listener = listener;
- }
-
- @Override
- protected void handleStartProgressEvent(int actionCode, MotionEvent event) {
- switch (actionCode) {
- case MotionEvent.ACTION_POINTER_DOWN:
- // At least the second finger is on screen now
-
- resetState(); // In case we missed an UP/CANCEL event
- prevEvent = MotionEvent.obtain(event);
- timeDelta = 0;
-
- updateStateByEvent(event);
-
- // See if we have a sloppy gesture
- sloppyGesture = isSloppyGesture(event);
- if (!sloppyGesture) {
- // No, start gesture now
- gestureInProgress = listener.onShoveBegin(this);
- }
- break;
-
- case MotionEvent.ACTION_MOVE:
- if (!sloppyGesture) {
- break;
- }
-
- // See if we still have a sloppy gesture
- sloppyGesture = isSloppyGesture(event);
- if (!sloppyGesture) {
- // No, start normal gesture now
- gestureInProgress = listener.onShoveBegin(this);
- }
-
- break;
-
- case MotionEvent.ACTION_POINTER_UP:
- if (!sloppyGesture) {
- break;
- }
-
- break;
- }
- }
-
- @Override
- protected void handleInProgressEvent(int actionCode, MotionEvent event) {
- switch (actionCode) {
- case MotionEvent.ACTION_POINTER_UP:
- // Gesture ended but
- updateStateByEvent(event);
-
- if (!sloppyGesture) {
- listener.onShoveEnd(this);
- }
-
- resetState();
- break;
-
- case MotionEvent.ACTION_CANCEL:
- if (!sloppyGesture) {
- listener.onShoveEnd(this);
- }
-
- resetState();
- break;
-
- case MotionEvent.ACTION_MOVE:
- updateStateByEvent(event);
-
- // Only accept the event if our relative pressure is within
- // a certain limit. This can help filter shaky data as a
- // finger is lifted. Also check that shove is meaningful.
- if (currPressure / prevPressure > PRESSURE_THRESHOLD
- && Math.abs(getShovePixelsDelta()) > 0.5f) {
- final boolean updatePrevious = listener.onShove(this);
- if (updatePrevious) {
- prevEvent.recycle();
- prevEvent = MotionEvent.obtain(event);
- }
- }
- break;
- }
- }
-
- @Override
- protected void resetState() {
- super.resetState();
- sloppyGesture = false;
- prevAverageY = 0.0f;
- currAverageY = 0.0f;
- }
-
- @Override
- protected void updateStateByEvent(MotionEvent curr) {
- super.updateStateByEvent(curr);
-
- final MotionEvent prev = prevEvent;
- float py0 = prev.getY(0);
- float py1 = prev.getY(1);
- prevAverageY = (py0 + py1) / 2.0f;
-
- float cy0 = curr.getY(0);
- float cy1 = curr.getY(1);
- currAverageY = (cy0 + cy1) / 2.0f;
- }
-
- @Override
- protected boolean isSloppyGesture(MotionEvent event) {
- boolean sloppy = super.isSloppyGesture(event);
- if (sloppy) {
- return true;
- }
-
- // If it's not traditionally sloppy, we check if the angle between
- // fingers
- // is acceptable.
- double angle = Math.abs(Math.atan2(currFingerDiffY, currFingerDiffX));
- // about 20 degrees, left or right
- return !((0.0f < angle && angle < 0.35f) || 2.79f < angle
- && angle < Math.PI);
- }
-
- /**
- * Return the distance in pixels from the previous shove event to the
- * current event.
- *
- * @return The current distance in pixels.
- */
- public float getShovePixelsDelta() {
- return currAverageY - prevAverageY;
- }
-}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/TwoFingerGestureDetector.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/TwoFingerGestureDetector.java
deleted file mode 100644
index db492b6556..0000000000
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/TwoFingerGestureDetector.java
+++ /dev/null
@@ -1,224 +0,0 @@
-package com.almeros.android.multitouch.gesturedetectors;
-
-import android.content.Context;
-import android.graphics.PointF;
-import android.util.DisplayMetrics;
-import android.view.MotionEvent;
-import android.view.ViewConfiguration;
-
-/**
- * @author Almer Thie (code.almeros.com) Copyright (c) 2013, Almer Thie
- * (code.almeros.com)
- * <p>
- * All rights reserved.
- * <p>
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * <p>
- * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * <p>
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
- * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
- * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
- * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
- * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
- * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- */
-public abstract class TwoFingerGestureDetector extends BaseGestureDetector {
-
- private final float edgeSlop;
-
- protected float prevFingerDiffX;
- protected float prevFingerDiffY;
- protected float currFingerDiffX;
- protected float currFingerDiffY;
-
- private float currLen;
- private float prevLen;
-
- private PointF focus;
-
- public TwoFingerGestureDetector(Context context) {
- super(context);
-
- ViewConfiguration config = ViewConfiguration.get(context);
-
- edgeSlop = config.getScaledEdgeSlop();
- }
-
- @Override
- protected abstract void handleStartProgressEvent(int actionCode,
- MotionEvent event);
-
- @Override
- protected abstract void handleInProgressEvent(int actionCode,
- MotionEvent event);
-
- protected void updateStateByEvent(MotionEvent curr) {
- super.updateStateByEvent(curr);
-
- final MotionEvent prev = prevEvent;
-
- currLen = -1;
- prevLen = -1;
-
- // Previous
- final float px0 = prev.getX(0);
- final float py0 = prev.getY(0);
- final float px1 = prev.getX(1);
- final float py1 = prev.getY(1);
- final float pvx = px1 - px0;
- final float pvy = py1 - py0;
- prevFingerDiffX = pvx;
- prevFingerDiffY = pvy;
-
- // Current
- final float cx0 = curr.getX(0);
- final float cy0 = curr.getY(0);
- final float cx1 = curr.getX(1);
- final float cy1 = curr.getY(1);
- final float cvx = cx1 - cx0;
- final float cvy = cy1 - cy0;
- currFingerDiffX = cvx;
- currFingerDiffY = cvy;
- focus = determineFocalPoint(curr);
- }
-
- /**
- * Return the current distance between the two pointers forming the gesture
- * in progress.
- *
- * @return Distance between pointers in pixels.
- */
- public float getCurrentSpan() {
- if (currLen == -1) {
- final float cvx = currFingerDiffX;
- final float cvy = currFingerDiffY;
- currLen = (float) Math.sqrt(cvx * cvx + cvy * cvy);
- }
- return currLen;
- }
-
- /**
- * Return the previous distance between the two pointers forming the gesture
- * in progress.
- *
- * @return Previous distance between pointers in pixels.
- */
- public float getPreviousSpan() {
- if (prevLen == -1) {
- final float pvx = prevFingerDiffX;
- final float pvy = prevFingerDiffY;
- prevLen = (float) Math.sqrt(pvx * pvx + pvy * pvy);
- }
- return prevLen;
- }
-
- /**
- * MotionEvent has no getRawX(int) method; simulate it pending future API
- * approval.
- *
- * @param event Motion Event
- * @param pointerIndex Pointer Index
- * @return Raw x value or 0
- */
- protected static float getRawX(MotionEvent event, int pointerIndex) {
- float offset = event.getRawX() - event.getX();
- if (pointerIndex < event.getPointerCount()) {
- return event.getX(pointerIndex) + offset;
- }
- return 0.0f;
- }
-
- /**
- * MotionEvent has no getRawY(int) method; simulate it pending future API
- * approval.
- *
- * @param event Motion Event
- * @param pointerIndex Pointer Index
- * @return Raw y value or 0
- */
- protected static float getRawY(MotionEvent event, int pointerIndex) {
- float offset = event.getRawY() - event.getY();
- if (pointerIndex < event.getPointerCount()) {
- return event.getY(pointerIndex) + offset;
- }
- return 0.0f;
- }
-
- /**
- * Check if we have a sloppy gesture. Sloppy gestures can happen if the edge
- * of the user's hand is touching the screen, for example.
- *
- * @param event Motion Event
- * @return {@code true} if is sloppy gesture, {@code false} if not
- */
- protected boolean isSloppyGesture(MotionEvent event) {
- // As orientation can change, query the metrics in touch down
- DisplayMetrics metrics = context.getResources().getDisplayMetrics();
- float rightSlopEdge = metrics.widthPixels - edgeSlop;
- float bottomSlopEdge = metrics.heightPixels - edgeSlop;
-
- final float edgeSlop = this.edgeSlop;
-
- final float x0 = event.getRawX();
- final float y0 = event.getRawY();
- final float x1 = getRawX(event, 1);
- final float y1 = getRawY(event, 1);
-
- boolean p0sloppy = x0 < edgeSlop || y0 < edgeSlop || x0 > rightSlopEdge
- || y0 > bottomSlopEdge;
- boolean p1sloppy = x1 < edgeSlop || y1 < edgeSlop || x1 > rightSlopEdge
- || y1 > bottomSlopEdge;
-
- if (p0sloppy && p1sloppy) {
- return true;
- } else if (p0sloppy) {
- return true;
- } else if (p1sloppy) {
- return true;
- }
- return false;
- }
-
- /**
- * Determine (multi)finger focal point (a.k.a. center point between all
- * fingers)
- *
- * @param motionEvent Motion Event
- * @return PointF focal point
- */
- public static PointF determineFocalPoint(MotionEvent motionEvent) {
- // Number of fingers on screen
- final int pCount = motionEvent.getPointerCount();
- float x = 0.0f;
- float y = 0.0f;
-
- for (int i = 0; i < pCount; i++) {
- x += motionEvent.getX(i);
- y += motionEvent.getY(i);
- }
-
- return new PointF(x / pCount, y / pCount);
- }
-
- public float getFocusX() {
- return focus.x;
- }
-
- public float getFocusY() {
- return focus.y;
- }
-
-} \ No newline at end of file
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/package-info.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/package-info.java
deleted file mode 100644
index cff2f086dc..0000000000
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/package-info.java
+++ /dev/null
@@ -1,4 +0,0 @@
-/**
- * Do not use this package. Internal use only.
- */
-package com.almeros.android.multitouch.gesturedetectors;
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/EmptyLocationSource.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/EmptyLocationSource.java
deleted file mode 100644
index 8ea7e61eee..0000000000
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/EmptyLocationSource.java
+++ /dev/null
@@ -1,107 +0,0 @@
-package com.mapbox.mapboxsdk;
-
-
-import android.location.Location;
-import android.support.annotation.Nullable;
-
-import com.mapbox.mapboxsdk.location.LocationSource;
-import com.mapbox.services.android.telemetry.location.LocationEngine;
-import com.mapbox.services.android.telemetry.location.LocationEngineListener;
-
-class EmptyLocationSource extends LocationSource {
-
- /**
- * Activate the location engine which will connect whichever location provider you are using. You'll need to call
- * this before requesting user location updates using {@link LocationEngine#requestLocationUpdates()}.
- */
- @Override
- public void activate() {
- // Intentionally left empty
- }
-
- /**
- * Disconnect the location engine which is useful when you no longer need location updates or requesting the users
- * {@link LocationEngine#getLastLocation()}. Before deactivating, you'll need to stop request user location updates
- * using {@link LocationEngine#removeLocationUpdates()}.
- */
- @Override
- public void deactivate() {
- // Intentionally left empty
- }
-
- /**
- * Check if your location provider has been activated/connected. This is mainly used internally but is also useful in
- * the rare case when you'd like to know if your location engine is connected or not.
- *
- * @return boolean true if the location engine has been activated/connected, else false.
- */
- @Override
- public boolean isConnected() {
- return false;
- }
-
- /**
- * Returns the Last known location is the location provider is connected and location permissions are granted.
- *
- * @return the last known location
- */
- @Override
- @Nullable
- public Location getLastLocation() {
- return null;
- }
-
- /**
- * Request location updates to the location provider.
- */
- @Override
- public void requestLocationUpdates() {
- // Intentionally left empty
- }
-
- /**
- * Dismiss ongoing location update to the location provider.
- */
- @Override
- public void removeLocationUpdates() {
- // Intentionally left empty
- }
-
- /**
- * Invoked when the Location has changed.
- *
- * @param location the new location
- */
- @Override
- public void onLocationChanged(Location location) {
- // Intentionally left empty
- }
-
- /**
- * Useful when you'd like to add a location listener to handle location connections and update events. It is important
- * to note, that the callback will continue getting called even when your application isn't in the foreground.
- * Therefore, it is a good idea to use {@link LocationEngine#removeLocationEngineListener(LocationEngineListener)}
- * inside your activities {@code onStop()} method.
- *
- * @param listener A {@link LocationEngineListener} which you'd like to add to your location engine.
- * @since 2.0.0
- */
- @Override
- public void addLocationEngineListener(LocationEngineListener listener) {
- // Intentionally left empty
- }
-
- /**
- * If you no longer need your {@link LocationEngineListener} to be invoked with every location update, use this
- * method to remove it. It's also important to remove your listeners before the activity is destroyed to prevent any
- * potential memory leaks.
- *
- * @param listener the {@link LocationEngineListener} you'd like to remove from this {@link LocationEngine}.
- * @return true.
- * @since 2.0.0
- */
- @Override
- public boolean removeLocationEngineListener(LocationEngineListener listener) {
- return true;
- }
-}
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 b67b6e96f2..853ea1c11b 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
@@ -8,16 +8,12 @@ import android.support.annotation.NonNull;
import android.support.annotation.UiThread;
import android.text.TextUtils;
+import com.mapbox.android.core.location.LocationEngine;
+import com.mapbox.android.core.location.LocationEnginePriority;
+import com.mapbox.android.core.location.LocationEngineProvider;
import com.mapbox.mapboxsdk.constants.MapboxConstants;
import com.mapbox.mapboxsdk.exceptions.MapboxConfigurationException;
-import com.mapbox.mapboxsdk.location.LocationSource;
import com.mapbox.mapboxsdk.net.ConnectivityReceiver;
-import com.mapbox.services.android.telemetry.MapboxTelemetry;
-import com.mapbox.services.android.telemetry.location.LocationEngine;
-import com.mapbox.services.android.telemetry.location.LocationEnginePriority;
-import com.mapbox.services.android.telemetry.location.LocationEngineProvider;
-
-import timber.log.Timber;
/**
* The entry point to initialize the Mapbox Android SDK.
@@ -56,15 +52,9 @@ public final class Mapbox {
INSTANCE = new Mapbox(appContext, accessToken, locationEngine);
locationEngine.setPriority(LocationEnginePriority.NO_POWER);
- try {
- MapboxTelemetry.getInstance().initialize(
- appContext, accessToken, BuildConfig.MAPBOX_EVENTS_USER_AGENT, locationEngine);
- } catch (Exception exception) {
- Timber.e(exception, "Unable to instantiate Mapbox telemetry");
- }
-
ConnectivityReceiver.instance(appContext);
}
+
return INSTANCE;
}
@@ -146,22 +136,11 @@ public final class Mapbox {
}
/**
- * Returns a location source instance with empty methods.
- *
- * @return an empty location source implementation
- * @deprecated Replaced by {@link Mapbox#getLocationEngine()}
- */
- @Deprecated
- public static LocationSource getLocationSource() {
- return new EmptyLocationSource();
- }
-
-
- /**
* Returns the location engine used by the SDK.
*
* @return the location engine configured
*/
+ // TODO Do we need to expose this?
public static LocationEngine getLocationEngine() {
return INSTANCE.locationEngine;
}
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 c2f19072db..e732b2525f 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
@@ -8,7 +8,7 @@ import android.support.annotation.FloatRange;
import com.mapbox.mapboxsdk.R;
import com.mapbox.mapboxsdk.constants.MapboxConstants;
import com.mapbox.mapboxsdk.geometry.LatLng;
-import com.mapbox.services.android.telemetry.utils.MathUtils;
+import com.mapbox.mapboxsdk.utils.MathUtils;
/**
* Resembles the position, angle, zoom and tilt of the user's viewpoint.
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/GeometryConstants.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/GeometryConstants.java
index 1a7544d33a..7a17e500ca 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/GeometryConstants.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/GeometryConstants.java
@@ -37,6 +37,20 @@ public class GeometryConstants {
public static final double MIN_LATITUDE = -90;
/**
+ * This constant represents the latitude span when representing a geolocation.
+ *
+ * @since 6.0.0
+ */
+ public static final double LATITUDE_SPAN = 180;
+
+ /**
+ * This constant represents the longitude span when representing a geolocation.
+ *
+ * @since 6.0.0
+ */
+ public static final double LONGITUDE_SPAN = 360;
+
+ /**
* This constant represents the highest latitude value available to represent a geolocation.
*
* @since 6.0.0
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/MapboxConstants.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/MapboxConstants.java
index 60362dd2e9..6f263e4635 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/MapboxConstants.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/MapboxConstants.java
@@ -48,6 +48,31 @@ public class MapboxConstants {
public static final long VELOCITY_THRESHOLD_IGNORE_FLING = 1000;
/**
+ * Value by which the default rotation threshold will be increased when scaling
+ */
+ public static final float ROTATION_THRESHOLD_INCREASE_WHEN_SCALING = 25f;
+
+ /**
+ * Time within which user needs to lift fingers for velocity animation to start.
+ */
+ public static final long SCHEDULED_ANIMATION_TIMEOUT = 150L;
+
+ /**
+ * Minimum angular velocity for rotation animation
+ */
+ public static final float MINIMUM_ANGULAR_VELOCITY = 1.5f;
+
+ /**
+ * Maximum angular velocity for rotation animation
+ */
+ public static final float MAXIMUM_ANGULAR_VELOCITY = 20f;
+
+ /**
+ * Factor to calculate tilt change based on pixel change during shove gesture.
+ */
+ public static final float SHOVE_PIXEL_CHANGE_FACTOR = 0.1f;
+
+ /**
* The currently supported minimum zoom level.
*/
public static final float MINIMUM_ZOOM = 0.0f;
@@ -78,14 +103,14 @@ public class MapboxConstants {
public static final double MINIMUM_DIRECTION = 0;
/**
- * The currently used minimun scale factor to clamp to when a quick zoom gesture occurs
+ * The currently used minimum scale factor to clamp to when a quick zoom gesture occurs
*/
public static final float MINIMUM_SCALE_FACTOR_CLAMP = 0.00f;
/**
* The currently used maximum scale factor to clamp to when a quick zoom gesture occurs
*/
- public static final float MAXIMUM_SCALE_FACTOR_CLAMP = 0.45f;
+ public static final float MAXIMUM_SCALE_FACTOR_CLAMP = 0.15f;
/**
* Fragment Argument Key for MapboxMapOptions
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/MyBearingTracking.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/MyBearingTracking.java
index ceac862f39..c042b00577 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/MyBearingTracking.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/MyBearingTracking.java
@@ -22,7 +22,7 @@ import java.lang.annotation.RetentionPolicy;
*/
public class MyBearingTracking {
- @IntDef( {NONE, COMPASS, GPS})
+ @IntDef( {NONE, COMPASS, GPS, GPS_NORTH_FACING})
@Retention(RetentionPolicy.SOURCE)
public @interface Mode {
}
@@ -42,4 +42,9 @@ public class MyBearingTracking {
*/
public static final int GPS = 0x00000008;
+ /**
+ * Tracking the bearing of the user based on GPS data, but camera always faces north direction
+ */
+ public static final int GPS_NORTH_FACING = 0x0000000B;
+
}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/geometry/LatLngBounds.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/geometry/LatLngBounds.java
index cf647224ae..fc8d2ec8f0 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/geometry/LatLngBounds.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/geometry/LatLngBounds.java
@@ -29,6 +29,10 @@ public class LatLngBounds implements Parcelable {
* Construct a new LatLngBounds based on its corners, given in NESW
* order.
*
+ * If eastern longitude is smaller than the western one, bounds will include antimeridian.
+ * For example, if the NE point is (10, -170) and the SW point is (-10, 170), then bounds will span over 20 degrees
+ * and cross the antimeridian.
+ *
* @param northLatitude Northern Latitude
* @param eastLongitude Eastern Longitude
* @param southLatitude Southern Latitude
@@ -48,10 +52,9 @@ public class LatLngBounds implements Parcelable {
* @return the bounds representing the world
*/
public static LatLngBounds world() {
- return new LatLngBounds.Builder()
- .include(new LatLng(GeometryConstants.MAX_LATITUDE, GeometryConstants.MAX_LONGITUDE))
- .include(new LatLng(GeometryConstants.MIN_LATITUDE, GeometryConstants.MIN_LONGITUDE))
- .build();
+ return LatLngBounds.from(
+ GeometryConstants.MAX_LATITUDE, GeometryConstants.MAX_LONGITUDE,
+ GeometryConstants.MIN_LATITUDE, GeometryConstants.MIN_LONGITUDE);
}
/**
@@ -61,8 +64,21 @@ public class LatLngBounds implements Parcelable {
* @return LatLng center of this LatLngBounds
*/
public LatLng getCenter() {
- return new LatLng((this.latitudeNorth + this.latitudeSouth) / 2,
- (this.longitudeEast + this.longitudeWest) / 2);
+ double latCenter = (this.latitudeNorth + this.latitudeSouth) / 2.0;
+ double longCenter;
+
+ if (this.longitudeEast > this.longitudeWest) {
+ longCenter = (this.longitudeEast + this.longitudeWest) / 2;
+ } else {
+ double halfSpan = (GeometryConstants.LONGITUDE_SPAN + this.longitudeEast - this.longitudeWest) / 2.0;
+ longCenter = this.longitudeWest + halfSpan;
+ if (longCenter >= GeometryConstants.MAX_LONGITUDE) {
+ longCenter = this.longitudeEast - halfSpan;
+ }
+ return new LatLng(latCenter, longCenter);
+ }
+
+ return new LatLng(latCenter, longCenter);
}
/**
@@ -163,10 +179,26 @@ public class LatLngBounds implements Parcelable {
* @return Span distance
*/
public double getLongitudeSpan() {
- return Math.abs(this.longitudeEast - this.longitudeWest);
+ double longSpan = Math.abs(this.longitudeEast - this.longitudeWest);
+ if (this.longitudeEast > this.longitudeWest) {
+ return longSpan;
+ }
+
+ // shortest span contains antimeridian
+ return GeometryConstants.LONGITUDE_SPAN - longSpan;
}
+ static double getLongitudeSpan(final double longEast, final double longWest) {
+ double longSpan = Math.abs(longEast - longWest);
+ if (longEast > longWest) {
+ return longSpan;
+ }
+
+ // shortest span contains antimeridian
+ return GeometryConstants.LONGITUDE_SPAN - longSpan;
+ }
+
/**
* Validate if LatLngBounds is empty, determined if absolute distance is
*
@@ -196,21 +228,44 @@ public class LatLngBounds implements Parcelable {
*/
static LatLngBounds fromLatLngs(final List<? extends ILatLng> latLngs) {
double minLat = GeometryConstants.MAX_LATITUDE;
- double minLon = GeometryConstants.MAX_LONGITUDE;
double maxLat = GeometryConstants.MIN_LATITUDE;
- double maxLon = GeometryConstants.MIN_LONGITUDE;
+
+ double eastLon = latLngs.get(0).getLongitude();
+ double westLon = latLngs.get(1).getLongitude();
+ double lonSpan = Math.abs(eastLon - westLon);
+ if (lonSpan < GeometryConstants.LONGITUDE_SPAN / 2) {
+ if (eastLon < westLon) {
+ double temp = eastLon;
+ eastLon = westLon;
+ westLon = temp;
+ }
+ } else {
+ lonSpan = GeometryConstants.LONGITUDE_SPAN - lonSpan;
+ if (westLon < eastLon) {
+ double temp = eastLon;
+ eastLon = westLon;
+ westLon = temp;
+ }
+ }
for (final ILatLng gp : latLngs) {
final double latitude = gp.getLatitude();
- final double longitude = gp.getLongitude();
-
minLat = Math.min(minLat, latitude);
- minLon = Math.min(minLon, longitude);
maxLat = Math.max(maxLat, latitude);
- maxLon = Math.max(maxLon, longitude);
+
+ final double longitude = gp.getLongitude();
+ if (!containsLongitude(eastLon, westLon, longitude)) {
+ final double eastSpan = getLongitudeSpan(longitude, westLon);
+ final double westSpan = getLongitudeSpan(eastLon, longitude);
+ if (eastSpan <= westSpan) {
+ eastLon = longitude;
+ } else {
+ westLon = longitude;
+ }
+ }
}
- return new LatLngBounds(maxLat, maxLon, minLat, minLon);
+ return new LatLngBounds(maxLat, eastLon, minLat, westLon);
}
/**
@@ -322,6 +377,24 @@ public class LatLngBounds implements Parcelable {
return false;
}
+
+ private boolean containsLatitude(final double latitude) {
+ return (latitude <= this.latitudeNorth)
+ && (latitude >= this.latitudeSouth);
+ }
+
+ private boolean containsLongitude(final double longitude) {
+ return containsLongitude(this.longitudeEast, this.longitudeWest, longitude);
+ }
+
+ static boolean containsLongitude(final double eastLon, final double westLon, final double longitude) {
+ if (eastLon > westLon) {
+ return (longitude <= eastLon)
+ && (longitude >= westLon);
+ }
+ return (longitude < eastLon) || (longitude > westLon);
+ }
+
/**
* Determines whether this LatLngBounds contains a point.
*
@@ -329,12 +402,8 @@ public class LatLngBounds implements Parcelable {
* @return true, if the point is contained within the bounds
*/
public boolean contains(final ILatLng latLng) {
- final double latitude = latLng.getLatitude();
- final double longitude = latLng.getLongitude();
- return ((latitude <= this.latitudeNorth)
- && (latitude >= this.latitudeSouth))
- && ((longitude <= this.longitudeEast)
- && (longitude >= this.longitudeWest));
+ return containsLatitude(latLng.getLatitude())
+ && containsLongitude(latLng.getLongitude());
}
/**
@@ -344,7 +413,8 @@ public class LatLngBounds implements Parcelable {
* @return true, if the bounds is contained within the bounds
*/
public boolean contains(final LatLngBounds other) {
- return contains(other.getNorthEast()) && contains(other.getSouthWest());
+ return contains(other.getNorthEast())
+ && contains(other.getSouthWest());
}
/**
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationSource.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationSource.java
deleted file mode 100644
index 1313587158..0000000000
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationSource.java
+++ /dev/null
@@ -1,199 +0,0 @@
-package com.mapbox.mapboxsdk.location;
-
-import android.content.Context;
-import android.location.Location;
-import android.support.annotation.Nullable;
-
-import com.mapbox.mapboxsdk.Mapbox;
-import com.mapbox.services.android.telemetry.location.LocationEngine;
-import com.mapbox.services.android.telemetry.location.LocationEngineListener;
-import com.mapbox.services.android.telemetry.location.LocationEnginePriority;
-import com.mapzen.android.lost.api.LocationListener;
-import com.mapzen.android.lost.api.LocationRequest;
-import com.mapzen.android.lost.api.LocationServices;
-import com.mapzen.android.lost.api.LostApiClient;
-
-/**
- * LocationEngine using the Open Source Lost library
- * Manages locational updates. Contains methods to register and unregister location listeners.
- * <ul>
- * <li>You can register a LocationEngineListener with LocationSource#addLocationEngineListener(LocationEngineListener)
- * to receive location updates.</li>
- * <li> You can unregister a LocationEngineListener with
- * LocationEngine#removeLocationEngineListener(LocationEngineListener)} to stop receiving location updates.</li>
- * </ul>
- * <p>
- * Note: If registering a listener in your Activity.onStart() implementation, you should unregister it in
- * Activity.onStop(). (You won't receive location updates when paused, and this will cut down on unnecessary system
- * overhead). Do not unregister in Activity.onSaveInstanceState(), because this won't be called if the user moves back
- * in the history stack.
- * </p>
- *
- * @deprecated Use a {@link Mapbox#getLocationEngine()} instead.
- */
-@Deprecated
-public class LocationSource extends LocationEngine implements LostApiClient.ConnectionCallbacks, LocationListener {
-
- private Context context;
- private LostApiClient lostApiClient;
-
- /**
- * Constructs a location source instance.
- *
- * @param context the context from which the Application context will be derived.
- */
- public LocationSource(Context context) {
- super();
- this.context = context.getApplicationContext();
- lostApiClient = new LostApiClient.Builder(this.context)
- .addConnectionCallbacks(this)
- .build();
- }
-
- /**
- * Constructs a location source instance.
- * Needed to create empty location source implementations.
- */
- public LocationSource() {
- }
-
- /**
- * Activate the location engine which will connect whichever location provider you are using. You'll need to call
- * this before requesting user location updates using {@link LocationEngine#requestLocationUpdates()}.
- */
- @Override
- public void activate() {
- connect();
- }
-
- /**
- * Disconnect the location engine which is useful when you no longer need location updates or requesting the users
- * {@link LocationEngine#getLastLocation()}. Before deactivating, you'll need to stop request user location updates
- * using {@link LocationEngine#removeLocationUpdates()}.
- */
- @Override
- public void deactivate() {
- if (lostApiClient != null && lostApiClient.isConnected()) {
- lostApiClient.disconnect();
- }
- }
-
- /**
- * Check if your location provider has been activated/connected. This is mainly used internally but is also useful in
- * the rare case when you'd like to know if your location engine is connected or not.
- *
- * @return boolean true if the location engine has been activated/connected, else false.
- */
- @Override
- public boolean isConnected() {
- return lostApiClient.isConnected();
- }
-
- /**
- * Invoked when the location provider has connected.
- */
- @Override
- public void onConnected() {
- for (LocationEngineListener listener : locationListeners) {
- listener.onConnected();
- }
- }
-
- /**
- * Invoked when the location provider connection has been suspended.
- */
- @Override
- public void onConnectionSuspended() {
- // Empty
- }
-
- /**
- * Returns the Last known location is the location provider is connected and location permissions are granted.
- *
- * @return the last known location
- */
- @Override
- @Nullable
- public Location getLastLocation() {
- if (lostApiClient.isConnected()) {
- //noinspection MissingPermission
- return LocationServices.FusedLocationApi.getLastLocation(lostApiClient);
- }
- return null;
- }
-
- /**
- * Request location updates to the location provider.
- */
- @Override
- public void requestLocationUpdates() {
- LocationRequest request = LocationRequest.create();
-
- if (interval != null) {
- request.setInterval(interval);
- }
- if (fastestInterval != null) {
- request.setFastestInterval(fastestInterval);
- }
- if (smallestDisplacement != null) {
- request.setSmallestDisplacement(smallestDisplacement);
- }
-
- if (priority == LocationEnginePriority.NO_POWER) {
- request.setPriority(LocationRequest.PRIORITY_NO_POWER);
- } else if (priority == LocationEnginePriority.LOW_POWER) {
- request.setPriority(LocationRequest.PRIORITY_LOW_POWER);
- } else if (priority == LocationEnginePriority.BALANCED_POWER_ACCURACY) {
- request.setPriority(LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY);
- } else if (priority == LocationEnginePriority.HIGH_ACCURACY) {
- request.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
- }
-
- if (lostApiClient.isConnected()) {
- //noinspection MissingPermission
- LocationServices.FusedLocationApi.requestLocationUpdates(lostApiClient, request, this);
- }
- }
-
- /**
- * Dismiss ongoing location update to the location provider.
- */
- @Override
- public void removeLocationUpdates() {
- if (lostApiClient.isConnected()) {
- LocationServices.FusedLocationApi.removeLocationUpdates(lostApiClient, this);
- }
- }
-
- /**
- * Returns the location engine type.
- *
- * @return Lost type
- */
- @Override
- public Type obtainType() {
- return Type.LOST;
- }
-
- /**
- * Invoked when the Location has changed.
- *
- * @param location the new location
- */
- @Override
- public void onLocationChanged(Location location) {
- for (LocationEngineListener listener : locationListeners) {
- listener.onLocationChanged(location);
- }
- }
-
- private void connect() {
- if (lostApiClient != null) {
- if (lostApiClient.isConnected()) {
- onConnected();
- } else {
- lostApiClient.connect();
- }
- }
- }
-} \ No newline at end of file
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/package-info.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/package-info.java
deleted file mode 100644
index b27559e95e..0000000000
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/package-info.java
+++ /dev/null
@@ -1,4 +0,0 @@
-/**
- * Contains the Mapbox Maps Android Location API classes.
- */
-package com.mapbox.mapboxsdk.location; \ No newline at end of file
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/AttributionDialogManager.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/AttributionDialogManager.java
index b12757c81e..5ccd6bd795 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/AttributionDialogManager.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/AttributionDialogManager.java
@@ -1,5 +1,6 @@
package com.mapbox.mapboxsdk.maps;
+import android.app.Activity;
import android.app.AlertDialog;
import android.content.ActivityNotFoundException;
import android.content.Context;
@@ -16,7 +17,6 @@ import com.mapbox.mapboxsdk.attribution.Attribution;
import com.mapbox.mapboxsdk.attribution.AttributionParser;
import com.mapbox.mapboxsdk.camera.CameraPosition;
import com.mapbox.mapboxsdk.style.sources.Source;
-import com.mapbox.services.android.telemetry.MapboxTelemetry;
import java.util.ArrayList;
import java.util.List;
@@ -49,7 +49,17 @@ public class AttributionDialogManager implements View.OnClickListener, DialogInt
@Override
public void onClick(View view) {
attributionSet = new AttributionBuilder(mapboxMap).build();
- showAttributionDialog(getAttributionTitles());
+
+ boolean isActivityFinishing = false;
+ if (context instanceof Activity) {
+ isActivityFinishing = ((Activity) context).isFinishing();
+ }
+
+ // check is hosting activity isn't finishing
+ // https://github.com/mapbox/mapbox-gl-native/issues/11238
+ if (!isActivityFinishing) {
+ showAttributionDialog(getAttributionTitles());
+ }
}
protected void showAttributionDialog(String[] attributionTitles) {
@@ -88,7 +98,7 @@ public class AttributionDialogManager implements View.OnClickListener, DialogInt
builder.setPositiveButton(R.string.mapbox_attributionTelemetryPositive, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
- MapboxTelemetry.getInstance().setTelemetryEnabled(true);
+ Events.obtainTelemetry().enable();
dialog.cancel();
}
});
@@ -102,7 +112,7 @@ public class AttributionDialogManager implements View.OnClickListener, DialogInt
builder.setNegativeButton(R.string.mapbox_attributionTelemetryNegative, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
- MapboxTelemetry.getInstance().setTelemetryEnabled(false);
+ Events.obtainTelemetry().disable();
dialog.cancel();
}
});
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Events.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Events.java
new file mode 100644
index 0000000000..a68d4763ac
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Events.java
@@ -0,0 +1,36 @@
+package com.mapbox.mapboxsdk.maps;
+
+
+import com.mapbox.android.telemetry.MapboxTelemetry;
+import com.mapbox.android.telemetry.TelemetryEnabler;
+import com.mapbox.mapboxsdk.BuildConfig;
+import com.mapbox.mapboxsdk.Mapbox;
+
+class Events {
+ static final String TWO_FINGER_TAP = "TwoFingerTap";
+ static final String DOUBLE_TAP = "DoubleTap";
+ static final String SINGLE_TAP = "SingleTap";
+ static final String PAN = "Pan";
+ static final String PINCH = "Pinch";
+ static final String ROTATION = "Rotation";
+ static final String PITCH = "Pitch";
+ private MapboxTelemetry telemetry;
+
+ private Events() {
+ telemetry = new MapboxTelemetry(Mapbox.getApplicationContext(), Mapbox.getAccessToken(),
+ BuildConfig.MAPBOX_EVENTS_USER_AGENT);
+ TelemetryEnabler.State telemetryState = TelemetryEnabler.retrieveTelemetryStateFromPreferences();
+ if (TelemetryEnabler.State.NOT_INITIALIZED.equals(telemetryState)
+ || TelemetryEnabler.State.ENABLED.equals(telemetryState)) {
+ telemetry.enable();
+ }
+ }
+
+ private static class EventsHolder {
+ private static final Events INSTANCE = new Events();
+ }
+
+ static MapboxTelemetry obtainTelemetry() {
+ return EventsHolder.INSTANCE.telemetry;
+ }
+}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapGestureDetector.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapGestureDetector.java
index 1788cb4428..5f5a10d0d0 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapGestureDetector.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapGestureDetector.java
@@ -5,27 +5,31 @@ import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.PointF;
-import android.location.Location;
+import android.os.Handler;
import android.support.annotation.Nullable;
-import android.support.v4.view.GestureDetectorCompat;
-import android.support.v4.view.ScaleGestureDetectorCompat;
-import android.support.v4.view.animation.FastOutSlowInInterpolator;
import android.view.InputDevice;
import android.view.MotionEvent;
-import android.view.ScaleGestureDetector;
-import android.view.VelocityTracker;
-import android.view.ViewConfiguration;
-
-import com.almeros.android.multitouch.gesturedetectors.RotateGestureDetector;
-import com.almeros.android.multitouch.gesturedetectors.ShoveGestureDetector;
-import com.almeros.android.multitouch.gesturedetectors.TwoFingerGestureDetector;
+import android.view.animation.DecelerateInterpolator;
+
+import com.mapbox.android.gestures.AndroidGesturesManager;
+import com.mapbox.android.gestures.MoveGestureDetector;
+import com.mapbox.android.gestures.MultiFingerTapGestureDetector;
+import com.mapbox.android.gestures.RotateGestureDetector;
+import com.mapbox.android.gestures.ShoveGestureDetector;
+import com.mapbox.android.gestures.StandardGestureDetector;
+import com.mapbox.android.gestures.StandardScaleGestureDetector;
+import com.mapbox.android.telemetry.Event;
+import com.mapbox.android.telemetry.MapEventFactory;
+import com.mapbox.android.telemetry.MapState;
+import com.mapbox.mapboxsdk.R;
import com.mapbox.mapboxsdk.constants.MapboxConstants;
import com.mapbox.mapboxsdk.geometry.LatLng;
-import com.mapbox.services.android.telemetry.MapboxEvent;
-import com.mapbox.services.android.telemetry.MapboxTelemetry;
-import com.mapbox.services.android.telemetry.utils.MathUtils;
-import com.mapbox.services.android.telemetry.utils.TelemetryUtils;
+import com.mapbox.mapboxsdk.utils.MathUtils;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import static com.mapbox.mapboxsdk.maps.MapboxMap.OnCameraMoveStartedListener.REASON_API_ANIMATION;
@@ -33,24 +37,15 @@ import static com.mapbox.mapboxsdk.maps.MapboxMap.OnCameraMoveStartedListener.RE
/**
* Manages gestures events on a MapView.
- * <p>
- * Relies on gesture detection code in almeros.android.multitouch.gesturedetectors.
- * </p>
*/
final class MapGestureDetector {
private final Transform transform;
private final Projection projection;
private final UiSettings uiSettings;
- private final TrackingSettings trackingSettings;
private final AnnotationManager annotationManager;
private final CameraChangeDispatcher cameraChangeDispatcher;
- private GestureDetectorCompat gestureDetector;
- private ScaleGestureDetector scaleGestureDetector;
- private RotateGestureDetector rotateGestureDetector;
- private ShoveGestureDetector shoveGestureDetector;
-
// deprecated map touch API
private MapboxMap.OnMapClickListener onMapClickListener;
private MapboxMap.OnMapLongClickListener onMapLongClickListener;
@@ -70,43 +65,73 @@ final class MapGestureDetector {
private final CopyOnWriteArrayList<MapboxMap.OnScrollListener> onScrollListenerList
= new CopyOnWriteArrayList<>();
- private PointF focalPoint;
+ private final CopyOnWriteArrayList<MapboxMap.OnMoveListener> onMoveListenerList
+ = new CopyOnWriteArrayList<>();
- private boolean twoTap;
- private boolean quickZoom;
- private boolean tiltGestureOccurred;
- private boolean scrollGestureOccurred;
+ private final CopyOnWriteArrayList<MapboxMap.OnRotateListener> onRotateListenerList
+ = new CopyOnWriteArrayList<>();
- private boolean scaleGestureOccurred;
- private boolean recentScaleGestureOccurred;
- private long scaleBeginTime;
+ private final CopyOnWriteArrayList<MapboxMap.OnScaleListener> onScaleListenerList
+ = new CopyOnWriteArrayList<>();
- private boolean scaleAnimating;
- private boolean rotateAnimating;
+ private final CopyOnWriteArrayList<MapboxMap.OnShoveListener> onShoveListenerList
+ = new CopyOnWriteArrayList<>();
- private VelocityTracker velocityTracker;
- private boolean wasZoomingIn;
- private boolean wasClockwiseRotating;
- private boolean rotateGestureOccurred;
+ /**
+ * User-set focal point.
+ */
+ private PointF focalPoint;
+
+ private AndroidGesturesManager gesturesManager;
+ private boolean executeDoubleTap;
+
+ private Animator scaleAnimator;
+ private Animator rotateAnimator;
+ private final List<Animator> scheduledAnimators = new ArrayList<>();
+
+ /**
+ * Cancels scheduled velocity animations if user doesn't lift fingers within
+ * {@link MapboxConstants#SCHEDULED_ANIMATION_TIMEOUT}
+ */
+ private Handler animationsTimeoutHandler = new Handler();
MapGestureDetector(Context context, Transform transform, Projection projection, UiSettings uiSettings,
- TrackingSettings trackingSettings, AnnotationManager annotationManager,
- CameraChangeDispatcher cameraChangeDispatcher) {
+ AnnotationManager annotationManager, CameraChangeDispatcher cameraChangeDispatcher) {
this.annotationManager = annotationManager;
this.transform = transform;
this.projection = projection;
this.uiSettings = uiSettings;
- this.trackingSettings = trackingSettings;
this.cameraChangeDispatcher = cameraChangeDispatcher;
- // Touch gesture detectors
+ // Checking for context != null for testing purposes
if (context != null) {
- gestureDetector = new GestureDetectorCompat(context, new GestureListener());
- gestureDetector.setIsLongpressEnabled(true);
- scaleGestureDetector = new ScaleGestureDetector(context, new ScaleGestureListener());
- ScaleGestureDetectorCompat.setQuickScaleEnabled(scaleGestureDetector, true);
- rotateGestureDetector = new RotateGestureDetector(context, new RotateGestureListener());
- shoveGestureDetector = new ShoveGestureDetector(context, new ShoveGestureListener());
+ gesturesManager = new AndroidGesturesManager(context);
+
+ Set<Integer> shoveScaleSet = new HashSet<>();
+ shoveScaleSet.add(AndroidGesturesManager.GESTURE_TYPE_SHOVE);
+ shoveScaleSet.add(AndroidGesturesManager.GESTURE_TYPE_SCALE);
+
+ Set<Integer> shoveRotateSet = new HashSet<>();
+ shoveRotateSet.add(AndroidGesturesManager.GESTURE_TYPE_SHOVE);
+ shoveRotateSet.add(AndroidGesturesManager.GESTURE_TYPE_ROTATE);
+
+ Set<Integer> ScaleLongPressSet = new HashSet<>();
+ ScaleLongPressSet.add(AndroidGesturesManager.GESTURE_TYPE_SCALE);
+ ScaleLongPressSet.add(AndroidGesturesManager.GESTURE_TYPE_LONG_PRESS);
+
+ gesturesManager.setMutuallyExclusiveGestures(shoveScaleSet, shoveRotateSet, ScaleLongPressSet);
+
+ gesturesManager.setStandardGestureListener(new StandardGestureListener());
+ gesturesManager.setMoveGestureListener(new MoveGestureListener());
+ gesturesManager.setStandardScaleGestureListener(new ScaleGestureListener(
+ context.getResources().getDimension(R.dimen.mapbox_minimum_scale_velocity)
+ ));
+ gesturesManager.setRotateGestureListener(new RotateGestureListener(
+ context.getResources().getDimension(R.dimen.mapbox_minimum_scale_span_when_rotating),
+ context.getResources().getDimension(R.dimen.mapbox_minimum_angular_velocity)
+ ));
+ gesturesManager.setShoveGestureListener(new ShoveGestureListener());
+ gesturesManager.setMultiFingerTapGestureListener(new TapGestureListener());
}
}
@@ -133,8 +158,9 @@ final class MapGestureDetector {
/**
* Get the current active gesture focal point.
* <p>
- * This could be either the user provided focal point in {@link UiSettings#setFocalPoint(PointF)} or the focal point
- * defined as a result of {@link TrackingSettings#setMyLocationEnabled(boolean)}.
+ * This could be either the user provided focal point in
+ * {@link UiSettings#setFocalPoint(PointF)}or <code>null</code>.
+ * If it's <code>null</code>, gestures will use focal pointed returned by the detector.
* </p>
*
* @return the current active gesture focal point.
@@ -145,128 +171,84 @@ final class MapGestureDetector {
}
/**
- * Given coordinates from a gesture, use the current projection to translate it into
- * a Location object.
- *
- * @param x coordinate
- * @param y coordinate
- * @return location
- */
- private Location getLocationFromGesture(float x, float y) {
- LatLng latLng = projection.fromScreenLocation(new PointF(x, y));
- return TelemetryUtils.buildLocation(latLng.getLongitude(), latLng.getLatitude());
- }
-
- /**
* Called when user touches the screen, all positions are absolute.
* <p>
* Forwards event to the related gesture detectors.
* </p>
*
- * @param event the MotionEvent
+ * @param motionEvent the MotionEvent
* @return True if touch event is handled
*/
- boolean onTouchEvent(MotionEvent event) {
- // framework can return null motion events in edge cases #9432
- if (event == null) {
+ boolean onTouchEvent(MotionEvent motionEvent) {
+ // Framework can return null motion events in edge cases #9432
+ if (motionEvent == null) {
return false;
}
// Check and ignore non touch or left clicks
- if ((event.getButtonState() != 0) && (event.getButtonState() != MotionEvent.BUTTON_PRIMARY)) {
+ if ((motionEvent.getButtonState() != 0) && (motionEvent.getButtonState() != MotionEvent.BUTTON_PRIMARY)) {
return false;
}
- // Check two finger gestures first
- scaleGestureDetector.onTouchEvent(event);
- rotateGestureDetector.onTouchEvent(event);
- shoveGestureDetector.onTouchEvent(event);
+ boolean result = gesturesManager.onTouchEvent(motionEvent);
- // Handle two finger tap
- switch (event.getActionMasked()) {
+ switch (motionEvent.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
- if (velocityTracker == null) {
- velocityTracker = VelocityTracker.obtain();
- } else {
- velocityTracker.clear();
- }
- velocityTracker.addMovement(event);
- // First pointer down, reset scaleGestureOccurred, used to avoid triggering a fling after a scale gesture #7666
- recentScaleGestureOccurred = false;
+ cancelAnimators();
transform.setGestureInProgress(true);
break;
+ case MotionEvent.ACTION_UP:
+ transform.setGestureInProgress(false);
- case MotionEvent.ACTION_POINTER_DOWN:
- // Second pointer down
- twoTap = event.getPointerCount() == 2
- && uiSettings.isZoomGesturesEnabled();
- if (twoTap) {
- // Confirmed 2nd Finger Down
- MapboxTelemetry.getInstance().pushEvent(MapboxEventWrapper.buildMapClickEvent(
- getLocationFromGesture(event.getX(), event.getY()),
- MapboxEvent.GESTURE_TWO_FINGER_SINGLETAP, transform));
+ // Start all awaiting velocity animations
+ animationsTimeoutHandler.removeCallbacksAndMessages(null);
+ for (Animator animator : scheduledAnimators) {
+ animator.start();
}
+ scheduledAnimators.clear();
break;
- case MotionEvent.ACTION_POINTER_UP:
- // Second pointer up
+ case MotionEvent.ACTION_CANCEL:
+ scheduledAnimators.clear();
+ transform.setGestureInProgress(false);
break;
+ }
- case MotionEvent.ACTION_UP:
- // First pointer up
- long tapInterval = event.getEventTime() - event.getDownTime();
- boolean isTap = tapInterval <= ViewConfiguration.getTapTimeout();
- boolean inProgress = rotateGestureDetector.isInProgress()
- || scaleGestureDetector.isInProgress()
- || shoveGestureDetector.isInProgress();
-
- if (twoTap && isTap && !inProgress) {
- if (focalPoint != null) {
- transform.zoom(false, focalPoint);
- } else {
- PointF focalPoint = TwoFingerGestureDetector.determineFocalPoint(event);
- transform.zoom(false, focalPoint);
- }
- twoTap = false;
- return true;
- }
-
- // Scroll / Pan Has Stopped
- if (scrollGestureOccurred) {
- MapboxTelemetry.getInstance().pushEvent(MapboxEventWrapper.buildMapDragEndEvent(
- getLocationFromGesture(event.getX(), event.getY()), transform));
- scrollGestureOccurred = false;
+ return result;
+ }
- if (!scaleAnimating && !rotateAnimating) {
- cameraChangeDispatcher.onCameraIdle();
- }
- }
+ void cancelAnimators() {
+ if (scaleAnimator != null) {
+ scaleAnimator.cancel();
+ }
+ if (rotateAnimator != null) {
+ rotateAnimator.cancel();
+ }
- twoTap = false;
- transform.setGestureInProgress(false);
- if (velocityTracker != null) {
- velocityTracker.recycle();
- }
- velocityTracker = null;
- break;
+ animationsTimeoutHandler.removeCallbacksAndMessages(null);
+ scheduledAnimators.clear();
+ }
- case MotionEvent.ACTION_CANCEL:
- twoTap = false;
- transform.setGestureInProgress(false);
- if (velocityTracker != null) {
- velocityTracker.recycle();
- }
- velocityTracker = null;
- break;
- case MotionEvent.ACTION_MOVE:
- if (velocityTracker != null) {
- velocityTracker.addMovement(event);
- velocityTracker.computeCurrentVelocity(1000);
- }
- break;
+ /**
+ * Posted on main thread with {@link #animationsTimeoutHandler}. Cancels all scheduled animators if needed.
+ */
+ private Runnable cancelAnimatorsRunnable = new Runnable() {
+ @Override
+ public void run() {
+ cancelAnimators();
}
+ };
- return gestureDetector.onTouchEvent(event);
+ /**
+ * Schedules a velocity animator to be executed when user lift fingers,
+ * unless canceled by the {@link #cancelAnimatorsRunnable}.
+ *
+ * @param animator animator ot be scheduled
+ */
+ private void scheduleAnimator(Animator animator) {
+ scheduledAnimators.add(animator);
+ animationsTimeoutHandler.removeCallbacksAndMessages(null);
+ animationsTimeoutHandler.postDelayed(cancelAnimatorsRunnable, MapboxConstants.SCHEDULED_ANIMATION_TIMEOUT);
}
/**
@@ -275,7 +257,7 @@ final class MapGestureDetector {
* Examples of such events are mouse scroll events, mouse moves, joystick & trackpad.
* </p>
*
- * @param event The MotionEvent occured
+ * @param event The MotionEvent occurred
* @return True is the event is handled
*/
boolean onGenericMotionEvent(MotionEvent event) {
@@ -297,7 +279,7 @@ final class MapGestureDetector {
float scrollDist = event.getAxisValue(MotionEvent.AXIS_VSCROLL);
// Scale the map by the appropriate power of two factor
- transform.zoomBy(scrollDist, event.getX(), event.getY());
+ transform.zoomBy(scrollDist, new PointF(event.getX(), event.getY()));
return true;
@@ -311,57 +293,14 @@ final class MapGestureDetector {
return false;
}
- /**
- * Responsible for handling one finger gestures.
- */
- private class GestureListener extends android.view.GestureDetector.SimpleOnGestureListener {
-
+ private final class StandardGestureListener extends StandardGestureDetector.SimpleStandardOnGestureListener {
@Override
- public boolean onDown(MotionEvent event) {
- return true;
- }
-
- @Override
- public boolean onDoubleTapEvent(MotionEvent e) {
- if (!uiSettings.isZoomGesturesEnabled() || !uiSettings.isDoubleTapGesturesEnabled()) {
- return false;
- }
-
- switch (e.getAction()) {
- case MotionEvent.ACTION_DOWN:
- break;
- case MotionEvent.ACTION_MOVE:
- break;
- case MotionEvent.ACTION_UP:
- if (quickZoom) {
- cameraChangeDispatcher.onCameraIdle();
- quickZoom = false;
- break;
- }
-
- // notify camera change listener
- cameraChangeDispatcher.onCameraMoveStarted(REASON_API_GESTURE);
-
- // Single finger double tap
- if (focalPoint != null) {
- // User provided focal point
- transform.zoom(true, focalPoint);
- } else {
- // Zoom in on gesture
- transform.zoom(true, new PointF(e.getX(), e.getY()));
- }
- MapboxTelemetry.getInstance().pushEvent(MapboxEventWrapper.buildMapClickEvent(
- getLocationFromGesture(e.getX(), e.getY()),
- MapboxEvent.GESTURE_DOUBLETAP, transform));
- break;
- }
-
+ public boolean onDown(MotionEvent motionEvent) {
return true;
}
@Override
public boolean onSingleTapUp(MotionEvent motionEvent) {
- // Cancel any animation
transform.cancelTransitions();
return true;
}
@@ -380,27 +319,51 @@ final class MapGestureDetector {
notifyOnMapClickListeners(tapPoint);
}
- MapboxTelemetry.getInstance().pushEvent(MapboxEventWrapper.buildMapClickEvent(
- getLocationFromGesture(motionEvent.getX(), motionEvent.getY()),
- MapboxEvent.GESTURE_SINGLETAP, transform));
+ sendTelemetryEvent(Events.SINGLE_TAP, new PointF(motionEvent.getX(), motionEvent.getY()));
return true;
}
@Override
- public void onLongPress(MotionEvent motionEvent) {
- PointF longClickPoint = new PointF(motionEvent.getX(), motionEvent.getY());
+ public boolean onDoubleTapEvent(MotionEvent motionEvent) {
+ int action = motionEvent.getActionMasked();
+ if (action == MotionEvent.ACTION_DOWN) {
+ executeDoubleTap = true;
+ }
+ if (motionEvent.getActionMasked() == MotionEvent.ACTION_UP) {
+ if (!uiSettings.isZoomGesturesEnabled() || !uiSettings.isDoubleTapGesturesEnabled() || !executeDoubleTap) {
+ return false;
+ }
+
+ transform.cancelTransitions();
+ cameraChangeDispatcher.onCameraMoveStarted(REASON_API_GESTURE);
+
+ // Single finger double tap
+ if (focalPoint != null) {
+ // User provided focal point
+ transform.zoomIn(focalPoint);
+ } else {
+ // Zoom in on gesture
+ transform.zoomIn(new PointF(motionEvent.getX(), motionEvent.getY()));
+ }
- if (!quickZoom) {
- notifyOnMapLongClickListeners(longClickPoint);
+ sendTelemetryEvent(Events.DOUBLE_TAP, new PointF(motionEvent.getX(), motionEvent.getY()));
+
+ return true;
}
+ return super.onDoubleTapEvent(motionEvent);
+ }
+
+ @Override
+ public void onLongPress(MotionEvent motionEvent) {
+ PointF longClickPoint = new PointF(motionEvent.getX(), motionEvent.getY());
+ notifyOnMapLongClickListeners(longClickPoint);
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
- if ((!trackingSettings.isScrollGestureCurrentlyEnabled()) || recentScaleGestureOccurred) {
+ if ((!uiSettings.isScrollGesturesEnabled())) {
// don't allow a fling is scroll is disabled
- // and ignore when a scale gesture has occurred
return false;
}
@@ -413,11 +376,7 @@ final class MapGestureDetector {
return false;
}
- trackingSettings.resetTrackingModesIfRequired(true, false, false);
-
- // cancel any animation
transform.cancelTransitions();
-
cameraChangeDispatcher.onCameraMoveStarted(REASON_API_GESTURE);
// tilt results in a bigger translation, limiting input for #5281
@@ -433,222 +392,149 @@ final class MapGestureDetector {
transform.moveBy(offsetX, offsetY, animationTime);
notifyOnFlingListeners();
+
return true;
}
+ }
- // Called for drags
+ private final class MoveGestureListener extends MoveGestureDetector.SimpleOnMoveGestureListener {
@Override
- public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
- if (!trackingSettings.isScrollGestureCurrentlyEnabled()) {
+ public boolean onMoveBegin(MoveGestureDetector detector) {
+ if (!uiSettings.isScrollGesturesEnabled()) {
return false;
}
- if (tiltGestureOccurred) {
- return false;
- }
+ transform.cancelTransitions();
+ cameraChangeDispatcher.onCameraMoveStarted(REASON_API_GESTURE);
- if (!scrollGestureOccurred) {
- scrollGestureOccurred = true;
+ sendTelemetryEvent(Events.PAN, detector.getFocalPoint());
- // Cancel any animation
- if (!scaleGestureOccurred) {
- transform.cancelTransitions();
- cameraChangeDispatcher.onCameraMoveStarted(REASON_API_GESTURE);
- }
+ notifyOnMoveBeginListeners(detector);
- MapboxTelemetry.getInstance().pushEvent(MapboxEventWrapper.buildMapClickEvent(
- getLocationFromGesture(e1.getX(), e1.getY()),
- MapboxEvent.GESTURE_PAN_START, transform));
- }
+ return true;
+ }
- // reset tracking if needed
- trackingSettings.resetTrackingModesIfRequired(true, false, false);
+ @Override
+ public boolean onMove(MoveGestureDetector detector, float distanceX, float distanceY) {
+ // dispatching start even once more if another detector ended, and this one didn't
+ cameraChangeDispatcher.onCameraMoveStarted(CameraChangeDispatcher.REASON_API_GESTURE);
// Scroll the map
transform.moveBy(-distanceX, -distanceY, 0 /*no duration*/);
notifyOnScrollListeners();
+ notifyOnMoveListeners(detector);
return true;
}
- }
- void notifyOnMapClickListeners(PointF tapPoint) {
- // deprecated API
- if (onMapClickListener != null) {
- onMapClickListener.onMapClick(projection.fromScreenLocation(tapPoint));
- }
-
- // new API
- for (MapboxMap.OnMapClickListener listener : onMapClickListenerList) {
- listener.onMapClick(projection.fromScreenLocation(tapPoint));
+ @Override
+ public void onMoveEnd(MoveGestureDetector detector, float velocityX, float velocityY) {
+ cameraChangeDispatcher.onCameraIdle();
+ notifyOnMoveEndListeners(detector);
}
}
- void notifyOnMapLongClickListeners(PointF longClickPoint) {
- // deprecated API
- if (onMapLongClickListener != null) {
- onMapLongClickListener.onMapLongClick(projection.fromScreenLocation(longClickPoint));
- }
+ private final class ScaleGestureListener extends StandardScaleGestureDetector.SimpleStandardOnScaleGestureListener {
- // new API
- for (MapboxMap.OnMapLongClickListener listener : onMapLongClickListenerList) {
- listener.onMapLongClick(projection.fromScreenLocation(longClickPoint));
- }
- }
+ private final float minimumVelocity;
- void notifyOnFlingListeners() {
- // deprecated API
- if (onFlingListener != null) {
- onFlingListener.onFling();
- }
+ private PointF scaleFocalPoint;
+ private boolean quickZoom;
- // new API
- for (MapboxMap.OnFlingListener listener : onFlingListenerList) {
- listener.onFling();
+ ScaleGestureListener(float minimumVelocity) {
+ this.minimumVelocity = minimumVelocity;
}
- }
- void notifyOnScrollListeners() {
- //deprecated API
- if (onScrollListener != null) {
- onScrollListener.onScroll();
- }
+ @Override
+ public boolean onScaleBegin(StandardScaleGestureDetector detector) {
+ if (!uiSettings.isZoomGesturesEnabled()) {
+ return false;
+ }
- // new API
- for (MapboxMap.OnScrollListener listener : onScrollListenerList) {
- listener.onScroll();
- }
- }
+ transform.cancelTransitions();
+ cameraChangeDispatcher.onCameraMoveStarted(REASON_API_GESTURE);
- /**
- * Responsible for handling two finger gestures and double-tap drag gestures.
- */
- private class ScaleGestureListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
+ quickZoom = detector.getPointersCount() == 1;
+ if (quickZoom) {
+ // when quickzoom, dismiss double tap and disable move gesture
+ executeDoubleTap = false;
+ gesturesManager.getMoveGestureDetector().setEnabled(false);
+ }
- private static final int ANIMATION_TIME_MULTIPLIER = 77;
- private static final double ZOOM_DISTANCE_DIVIDER = 5;
+ // increase rotate angle threshold when scale is detected first
+ gesturesManager.getRotateGestureDetector().setAngleThreshold(
+ gesturesManager.getRotateGestureDetector().getDefaultAngleThreshold()
+ + MapboxConstants.ROTATION_THRESHOLD_INCREASE_WHEN_SCALING
+ );
- private float scaleFactor = 1.0f;
- private PointF scalePointBegin;
+ // setting focalPoint in #onScaleBegin() as well, because #onScale() might not get called before #onScaleEnd()
+ setScaleFocalPoint(detector);
- // Called when two fingers first touch the screen
- @Override
- public boolean onScaleBegin(ScaleGestureDetector detector) {
- if (!uiSettings.isZoomGesturesEnabled()) {
- return false;
- }
+ sendTelemetryEvent(Events.PINCH, scaleFocalPoint);
+
+ notifyOnScaleBeginListeners(detector);
- recentScaleGestureOccurred = true;
- scalePointBegin = new PointF(detector.getFocusX(), detector.getFocusY());
- scaleBeginTime = detector.getEventTime();
- MapboxTelemetry.getInstance().pushEvent(MapboxEventWrapper.buildMapClickEvent(
- getLocationFromGesture(detector.getFocusX(), detector.getFocusY()),
- MapboxEvent.GESTURE_PINCH_START, transform));
return true;
}
- // Called each time a finger moves
- // Called for pinch zooms and quickzooms/quickscales
@Override
- public boolean onScale(ScaleGestureDetector detector) {
- if (!uiSettings.isZoomGesturesEnabled()) {
- return super.onScale(detector);
- }
+ public boolean onScale(StandardScaleGestureDetector detector) {
+ // dispatching start even once more if another detector ended, and this one didn't
+ cameraChangeDispatcher.onCameraMoveStarted(CameraChangeDispatcher.REASON_API_GESTURE);
- wasZoomingIn = (Math.log(detector.getScaleFactor()) / Math.log(Math.PI / 2)) > 0;
- if (tiltGestureOccurred) {
- return false;
- }
+ setScaleFocalPoint(detector);
- // Ignore short touches in case it is a tap
- // Also ignore small scales
- long time = detector.getEventTime();
- long interval = time - scaleBeginTime;
- if (!scaleGestureOccurred && (interval <= ViewConfiguration.getTapTimeout())) {
- return false;
- }
+ float scaleFactor = detector.getScaleFactor();
+ double zoomBy = getNewZoom(scaleFactor, quickZoom);
+ transform.zoomBy(zoomBy, scaleFocalPoint);
- // If scale is large enough ignore a tap
- scaleFactor *= detector.getScaleFactor();
- if ((scaleFactor > 1.1f) || (scaleFactor < 0.9f)) {
- // notify camera change listener
- cameraChangeDispatcher.onCameraMoveStarted(REASON_API_GESTURE);
- scaleGestureOccurred = true;
- }
+ notifyOnScaleListeners(detector);
- if (!scaleGestureOccurred) {
- return false;
- }
-
- // Gesture is a quickzoom if there aren't two fingers
- if (!quickZoom && !twoTap) {
- cameraChangeDispatcher.onCameraMoveStarted(REASON_API_GESTURE);
- }
- quickZoom = !twoTap;
-
- // make an assumption here; if the zoom center is specified by the gesture, it's NOT going
- // to be in the center of the map. Therefore the zoom will translate the map center, so tracking
- // should be disabled.
- trackingSettings.resetTrackingModesIfRequired(!quickZoom, false, false);
- // Scale the map
- if (focalPoint != null) {
- // arround user provided focal point
- transform.zoomBy(Math.log(detector.getScaleFactor()) / Math.log(Math.PI / 2), focalPoint.x, focalPoint.y);
- } else if (quickZoom) {
- cameraChangeDispatcher.onCameraMove();
- // clamp scale factors we feed to core #7514
- float scaleFactor = detector.getScaleFactor();
- // around center map
- double zoomBy = Math.log(scaleFactor) / Math.log(Math.PI / 2);
- boolean negative = zoomBy < 0;
- zoomBy = MathUtils.clamp(Math.abs(zoomBy),
- MapboxConstants.MINIMUM_SCALE_FACTOR_CLAMP,
- MapboxConstants.MAXIMUM_SCALE_FACTOR_CLAMP);
- transform.zoomBy(negative ? -zoomBy : zoomBy, uiSettings.getWidth() / 2, uiSettings.getHeight() / 2);
- recentScaleGestureOccurred = true;
- } else {
- // around gesture
- transform.zoomBy(Math.log(detector.getScaleFactor()) / Math.log(Math.PI / 2),
- scalePointBegin.x, scalePointBegin.y);
- }
return true;
}
- // Called when fingers leave screen
@Override
- public void onScaleEnd(final ScaleGestureDetector detector) {
- if (velocityTracker == null) {
- return;
- }
+ public void onScaleEnd(StandardScaleGestureDetector detector, float velocityX, float velocityY) {
+ cameraChangeDispatcher.onCameraIdle();
- if (rotateGestureOccurred || quickZoom) {
- reset();
- return;
+ if (quickZoom) {
+ //if quickzoom, re-enabling move gesture detector
+ gesturesManager.getMoveGestureDetector().setEnabled(true);
}
- double velocityXY = Math.abs(velocityTracker.getYVelocity()) + Math.abs(velocityTracker.getXVelocity());
- if (velocityXY > MapboxConstants.VELOCITY_THRESHOLD_IGNORE_FLING / 2) {
- scaleAnimating = true;
- double zoomAddition = calculateScale(velocityXY);
+ // resetting default angle threshold
+ gesturesManager.getRotateGestureDetector().setAngleThreshold(
+ gesturesManager.getRotateGestureDetector().getDefaultAngleThreshold()
+ );
+
+ float velocityXY = Math.abs(velocityX) + Math.abs(velocityY);
+ if (velocityXY > minimumVelocity) {
+ double zoomAddition = calculateScale(velocityXY, detector.isScalingOut());
double currentZoom = transform.getRawZoom();
- long animationTime = (long) (Math.log(velocityXY) * ANIMATION_TIME_MULTIPLIER);
- createScaleAnimator(currentZoom, zoomAddition, animationTime).start();
- } else if (!scaleAnimating) {
- reset();
+ long animationTime = (long) (Math.abs(zoomAddition) * 1000 / 4);
+ scaleAnimator = createScaleAnimator(currentZoom, zoomAddition, animationTime);
+ scheduleAnimator(scaleAnimator);
}
+
+ notifyOnScaleEndListeners(detector);
}
- private void reset() {
- scaleAnimating = false;
- scaleGestureOccurred = false;
- scaleBeginTime = 0;
- scaleFactor = 1.0f;
- cameraChangeDispatcher.onCameraIdle();
+ private void setScaleFocalPoint(StandardScaleGestureDetector detector) {
+ if (focalPoint != null) {
+ // around user provided focal point
+ scaleFocalPoint = focalPoint;
+ } else if (quickZoom) {
+ // around center
+ scaleFocalPoint = new PointF(uiSettings.getWidth() / 2, uiSettings.getHeight() / 2);
+ } else {
+ // around gesture
+ scaleFocalPoint = detector.getFocalPoint();
+ }
}
- private double calculateScale(double velocityXY) {
- double zoomAddition = (float) (Math.log(velocityXY) / ZOOM_DISTANCE_DIVIDER);
- if (!wasZoomingIn) {
+ private double calculateScale(double velocityXY, boolean isScalingOut) {
+ double zoomAddition = (float) Math.log(velocityXY / 1000 + 1);
+ if (isScalingOut) {
zoomAddition = -zoomAddition;
}
return zoomAddition;
@@ -657,264 +543,387 @@ final class MapGestureDetector {
private Animator createScaleAnimator(double currentZoom, double zoomAddition, long animationTime) {
ValueAnimator animator = ValueAnimator.ofFloat((float) currentZoom, (float) (currentZoom + zoomAddition));
animator.setDuration(animationTime);
- animator.setInterpolator(new FastOutSlowInInterpolator());
+ animator.setInterpolator(new DecelerateInterpolator());
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
- transform.setZoom((Float) animation.getAnimatedValue(), scalePointBegin, 0, true);
+ transform.setZoom((Float) animation.getAnimatedValue(), scaleFocalPoint, 0);
}
});
+
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
+ transform.cancelTransitions();
cameraChangeDispatcher.onCameraMoveStarted(REASON_API_ANIMATION);
}
@Override
public void onAnimationCancel(Animator animation) {
- reset();
+ transform.cancelTransitions();
}
@Override
public void onAnimationEnd(Animator animation) {
- reset();
+ cameraChangeDispatcher.onCameraIdle();
}
});
return animator;
}
- }
- /**
- * Responsible for handling rotation gestures.
- */
- private class RotateGestureListener extends RotateGestureDetector.SimpleOnRotateGestureListener {
+ private double getNewZoom(float scaleFactor, boolean quickZoom) {
+ double zoomBy = Math.log(scaleFactor) / Math.log(Math.PI / 2);
+ if (quickZoom) {
+ // clamp scale factors we feed to core #7514
+ boolean negative = zoomBy < 0;
+ zoomBy = MathUtils.clamp(Math.abs(zoomBy),
+ MapboxConstants.MINIMUM_SCALE_FACTOR_CLAMP,
+ MapboxConstants.MAXIMUM_SCALE_FACTOR_CLAMP);
+ return negative ? -zoomBy : zoomBy;
+ }
+ return zoomBy;
+ }
+ }
- private static final float ROTATE_INVOKE_ANGLE = 15.30f;
- private static final float ROTATE_LIMITATION_ANGLE = 3.35f;
- private static final float ROTATE_LIMITATION_DURATION = ROTATE_LIMITATION_ANGLE * 1.85f;
+ private final class RotateGestureListener extends RotateGestureDetector.SimpleOnRotateGestureListener {
+ private PointF rotateFocalPoint;
+ private final float minimumScaleSpanWhenRotating;
+ private final float minimumAngularVelocity;
- private long beginTime = 0;
- private boolean started = false;
+ RotateGestureListener(float minimumScaleSpanWhenRotating, float minimumAngularVelocity) {
+ this.minimumScaleSpanWhenRotating = minimumScaleSpanWhenRotating;
+ this.minimumAngularVelocity = minimumAngularVelocity;
+ }
- // Called when two fingers first touch the screen
@Override
public boolean onRotateBegin(RotateGestureDetector detector) {
- if (!trackingSettings.isRotateGestureCurrentlyEnabled()) {
+ if (!uiSettings.isRotateGesturesEnabled()) {
return false;
}
- // notify camera change listener
+ transform.cancelTransitions();
cameraChangeDispatcher.onCameraMoveStarted(REASON_API_GESTURE);
- beginTime = detector.getEventTime();
- return true;
- }
+ // when rotation starts, interrupting scale and increasing the threshold
+ // to make rotation without scaling easier
+ gesturesManager.getStandardScaleGestureDetector().setSpanSinceStartThreshold(minimumScaleSpanWhenRotating);
+ gesturesManager.getStandardScaleGestureDetector().interrupt();
- // Called each time one of the two fingers moves
- // Called for rotation
- @Override
- public boolean onRotate(RotateGestureDetector detector) {
- if (!trackingSettings.isRotateGestureCurrentlyEnabled() || tiltGestureOccurred) {
- return false;
- }
+ // setting in #onRotateBegin() as well, because #onRotate() might not get called before #onRotateEnd()
+ setRotateFocalPoint(detector);
- // If rotate is large enough ignore a tap
- // Also is zoom already started, don't rotate
- float angle = detector.getRotationDegreesDelta();
- if (Math.abs(angle) >= ROTATE_INVOKE_ANGLE) {
- MapboxTelemetry.getInstance().pushEvent(MapboxEventWrapper.buildMapClickEvent(
- getLocationFromGesture(detector.getFocusX(), detector.getFocusY()),
- MapboxEvent.GESTURE_ROTATION_START, transform));
- started = true;
- }
+ sendTelemetryEvent(Events.ROTATION, rotateFocalPoint);
- if (!started) {
- return false;
- }
+ notifyOnRotateBeginListeners(detector);
- wasClockwiseRotating = detector.getRotationDegreesDelta() > 0;
- if (scaleBeginTime != 0) {
- rotateGestureOccurred = true;
- }
+ return true;
+ }
+
+ @Override
+ public boolean onRotate(RotateGestureDetector detector, float rotationDegreesSinceLast,
+ float rotationDegreesSinceFirst) {
+ // dispatching start even once more if another detector ended, and this one didn't
+ cameraChangeDispatcher.onCameraMoveStarted(CameraChangeDispatcher.REASON_API_GESTURE);
- // rotation constitutes translation of anything except the center of
- // rotation, so cancel both location and bearing tracking if required
- trackingSettings.resetTrackingModesIfRequired(true, true, false);
+ setRotateFocalPoint(detector);
// Calculate map bearing value
- double bearing = transform.getRawBearing() + angle;
+ double bearing = transform.getRawBearing() + rotationDegreesSinceLast;
// Rotate the map
- if (focalPoint != null) {
- // User provided focal point
- transform.setBearing(bearing, focalPoint.x, focalPoint.y);
- } else {
- // around gesture
- transform.setBearing(bearing, detector.getFocusX(), detector.getFocusY());
- }
+ transform.setBearing(bearing, rotateFocalPoint.x, rotateFocalPoint.y);
+
+ notifyOnRotateListeners(detector);
+
return true;
}
- // Called when the fingers leave the screen
@Override
- public void onRotateEnd(RotateGestureDetector detector) {
- long interval = detector.getEventTime() - beginTime;
- if ((!started && (interval <= ViewConfiguration.getTapTimeout())) || scaleAnimating || interval > 500) {
- reset();
+ public void onRotateEnd(RotateGestureDetector detector, float velocityX, float velocityY, float angularVelocity) {
+ cameraChangeDispatcher.onCameraIdle();
+
+ // resetting default scale threshold values
+ gesturesManager.getStandardScaleGestureDetector().setSpanSinceStartThreshold(
+ gesturesManager.getStandardScaleGestureDetector().getDefaultSpanSinceStartThreshold());
+
+ if (Math.abs(angularVelocity) < minimumAngularVelocity) {
return;
}
- double angularVelocity = calculateVelocityVector(detector);
- if (Math.abs(angularVelocity) > 0.001 && rotateGestureOccurred && !rotateAnimating) {
- animateRotateVelocity();
- } else if (!rotateAnimating) {
- reset();
- }
- }
+ boolean negative = angularVelocity < 0;
+ angularVelocity = (float) Math.pow(angularVelocity, 2);
+ angularVelocity = MathUtils.clamp(
+ angularVelocity, MapboxConstants.MINIMUM_ANGULAR_VELOCITY, MapboxConstants.MAXIMUM_ANGULAR_VELOCITY);
- private void reset() {
- beginTime = 0;
- started = false;
- rotateAnimating = false;
- rotateGestureOccurred = false;
+ long animationTime = (long) (Math.log(angularVelocity + 1) * 500);
- if (!twoTap) {
- cameraChangeDispatcher.onCameraIdle();
+ if (negative) {
+ angularVelocity = -angularVelocity;
}
- }
- private void animateRotateVelocity() {
- rotateAnimating = true;
- double currentRotation = transform.getRawBearing();
- double rotateAdditionDegrees = calculateVelocityInDegrees();
- createAnimator(currentRotation, rotateAdditionDegrees).start();
- }
+ rotateAnimator = createRotateAnimator(angularVelocity, animationTime);
+ scheduleAnimator(rotateAnimator);
- private double calculateVelocityVector(RotateGestureDetector detector) {
- return ((detector.getFocusX() * velocityTracker.getYVelocity())
- + (detector.getFocusY() * velocityTracker.getXVelocity()))
- / (Math.pow(detector.getFocusX(), 2) + Math.pow(detector.getFocusY(), 2));
+ notifyOnRotateEndListeners(detector);
}
- private double calculateVelocityInDegrees() {
- double angleRadians = Math.atan2(velocityTracker.getXVelocity(), velocityTracker.getYVelocity());
- double angle = angleRadians / (Math.PI / 180);
- if (angle <= 0) {
- angle += 360;
- }
-
- // limit the angle
- angle = angle / ROTATE_LIMITATION_ANGLE;
-
- // correct direction
- if (!wasClockwiseRotating) {
- angle = -angle;
+ private void setRotateFocalPoint(RotateGestureDetector detector) {
+ if (focalPoint != null) {
+ // User provided focal point
+ rotateFocalPoint = focalPoint;
+ } else {
+ // around gesture
+ rotateFocalPoint = detector.getFocalPoint();
}
-
- return angle;
}
- private Animator createAnimator(double currentRotation, double rotateAdditionDegrees) {
- ValueAnimator animator = ValueAnimator.ofFloat(
- (float) currentRotation,
- (float) (currentRotation + rotateAdditionDegrees)
- );
- animator.setDuration((long) (Math.abs(rotateAdditionDegrees) * ROTATE_LIMITATION_DURATION));
+ private Animator createRotateAnimator(float angularVelocity, long animationTime) {
+ ValueAnimator animator = ValueAnimator.ofFloat(angularVelocity, 0f);
+ animator.setDuration(animationTime);
+ animator.setInterpolator(new DecelerateInterpolator());
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
- transform.setBearing((Float) animation.getAnimatedValue());
+ transform.setBearing(
+ transform.getRawBearing() + (float) animation.getAnimatedValue(),
+ rotateFocalPoint.x, rotateFocalPoint.y,
+ 0L
+ );
}
});
+
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
+ transform.cancelTransitions();
cameraChangeDispatcher.onCameraMoveStarted(REASON_API_ANIMATION);
}
@Override
public void onAnimationCancel(Animator animation) {
- reset();
+ cameraChangeDispatcher.onCameraIdle();
}
@Override
public void onAnimationEnd(Animator animation) {
- reset();
+ cameraChangeDispatcher.onCameraIdle();
}
});
+
return animator;
}
}
- /**
- * Responsible for handling 2 finger shove gestures.
- */
- private class ShoveGestureListener implements ShoveGestureDetector.OnShoveGestureListener {
-
- private long beginTime = 0;
- private float totalDelta = 0.0f;
-
+ private final class ShoveGestureListener extends ShoveGestureDetector.SimpleOnShoveGestureListener {
@Override
public boolean onShoveBegin(ShoveGestureDetector detector) {
if (!uiSettings.isTiltGesturesEnabled()) {
return false;
}
- // notify camera change listener
+ transform.cancelTransitions();
cameraChangeDispatcher.onCameraMoveStarted(REASON_API_GESTURE);
+
+ sendTelemetryEvent(Events.PITCH, detector.getFocalPoint());
+
+ // disabling move gesture during shove
+ gesturesManager.getMoveGestureDetector().setEnabled(false);
+
+ notifyOnShoveBeginListeners(detector);
+
return true;
}
@Override
- public void onShoveEnd(ShoveGestureDetector detector) {
- beginTime = 0;
- totalDelta = 0.0f;
- tiltGestureOccurred = false;
+ public boolean onShove(ShoveGestureDetector detector, float deltaPixelsSinceLast, float deltaPixelsSinceStart) {
+ // dispatching start even once more if another detector ended, and this one didn't
+ cameraChangeDispatcher.onCameraMoveStarted(CameraChangeDispatcher.REASON_API_GESTURE);
+
+ // Get tilt value (scale and clamp)
+ double pitch = transform.getTilt();
+ pitch -= MapboxConstants.SHOVE_PIXEL_CHANGE_FACTOR * deltaPixelsSinceLast;
+ pitch = MathUtils.clamp(pitch, MapboxConstants.MINIMUM_TILT, MapboxConstants.MAXIMUM_TILT);
+
+ // Tilt the map
+ transform.setTilt(pitch);
+
+ notifyOnShoveListeners(detector);
+
+ return true;
}
@Override
- public boolean onShove(ShoveGestureDetector detector) {
- if (!uiSettings.isTiltGesturesEnabled()) {
- return false;
- }
+ public void onShoveEnd(ShoveGestureDetector detector, float velocityX, float velocityY) {
+ cameraChangeDispatcher.onCameraIdle();
- // Ignore short touches in case it is a tap
- // Also ignore small tilt
- long time = detector.getEventTime();
- long interval = time - beginTime;
- if (!tiltGestureOccurred && (interval <= ViewConfiguration.getTapTimeout())) {
- return false;
- }
+ // re-enabling move gesture
+ gesturesManager.getMoveGestureDetector().setEnabled(true);
- // If tilt is large enough ignore a tap
- // Also if zoom already started, don't tilt
- totalDelta += detector.getShovePixelsDelta();
- if (!tiltGestureOccurred && ((totalDelta > 10.0f) || (totalDelta < -10.0f))) {
- tiltGestureOccurred = true;
- beginTime = detector.getEventTime();
- MapboxTelemetry.getInstance().pushEvent(MapboxEventWrapper.buildMapClickEvent(
- getLocationFromGesture(detector.getFocusX(), detector.getFocusY()),
- MapboxEvent.GESTURE_PITCH_START, transform));
- }
+ notifyOnShoveEndListeners(detector);
+ }
+ }
- if (!tiltGestureOccurred) {
+ private final class TapGestureListener implements MultiFingerTapGestureDetector.OnMultiFingerTapGestureListener {
+ @Override
+ public boolean onMultiFingerTap(MultiFingerTapGestureDetector detector, int pointersCount) {
+ if (!uiSettings.isZoomGesturesEnabled() || pointersCount != 2) {
return false;
}
- // Get tilt value (scale and clamp)
- double pitch = transform.getTilt();
- pitch -= 0.1 * detector.getShovePixelsDelta();
- pitch = Math.max(MapboxConstants.MINIMUM_TILT, Math.min(MapboxConstants.MAXIMUM_TILT, pitch));
+ transform.cancelTransitions();
+ cameraChangeDispatcher.onCameraMoveStarted(REASON_API_GESTURE);
+
+ if (focalPoint != null) {
+ transform.zoomOut(focalPoint);
+ } else {
+ transform.zoomOut(detector.getFocalPoint());
+ }
- // Tilt the map
- transform.setTilt(pitch);
return true;
}
}
+ private void sendTelemetryEvent(String eventType, PointF focalPoint) {
+ if (isZoomValid(transform)) {
+ MapEventFactory mapEventFactory = new MapEventFactory();
+ LatLng latLng = projection.fromScreenLocation(focalPoint);
+ MapState state = new MapState(latLng.getLatitude(), latLng.getLongitude(), transform.getZoom());
+ state.setGesture(eventType);
+ Events.obtainTelemetry().push(mapEventFactory.createMapGestureEvent(Event.Type.MAP_CLICK, state));
+ }
+ }
+
+ private boolean isZoomValid(Transform transform) {
+ if (transform == null) {
+ return false;
+ }
+ double mapZoom = transform.getZoom();
+ return mapZoom >= MapboxConstants.MINIMUM_ZOOM && mapZoom <= MapboxConstants.MAXIMUM_ZOOM;
+ }
+
+ void notifyOnMapClickListeners(PointF tapPoint) {
+ // deprecated API
+ if (onMapClickListener != null) {
+ onMapClickListener.onMapClick(projection.fromScreenLocation(tapPoint));
+ }
+
+ // new API
+ for (MapboxMap.OnMapClickListener listener : onMapClickListenerList) {
+ listener.onMapClick(projection.fromScreenLocation(tapPoint));
+ }
+ }
+
+ void notifyOnMapLongClickListeners(PointF longClickPoint) {
+ // deprecated API
+ if (onMapLongClickListener != null) {
+ onMapLongClickListener.onMapLongClick(projection.fromScreenLocation(longClickPoint));
+ }
+
+ // new API
+ for (MapboxMap.OnMapLongClickListener listener : onMapLongClickListenerList) {
+ listener.onMapLongClick(projection.fromScreenLocation(longClickPoint));
+ }
+ }
+
+ void notifyOnFlingListeners() {
+ // deprecated API
+ if (onFlingListener != null) {
+ onFlingListener.onFling();
+ }
+
+ // new API
+ for (MapboxMap.OnFlingListener listener : onFlingListenerList) {
+ listener.onFling();
+ }
+ }
+
+ void notifyOnScrollListeners() {
+ //deprecated API
+ if (onScrollListener != null) {
+ onScrollListener.onScroll();
+ }
+
+ // new API
+ for (MapboxMap.OnScrollListener listener : onScrollListenerList) {
+ listener.onScroll();
+ }
+ }
+
+ void notifyOnMoveBeginListeners(MoveGestureDetector detector) {
+ for (MapboxMap.OnMoveListener listener : onMoveListenerList) {
+ listener.onMoveBegin(detector);
+ }
+ }
+
+ void notifyOnMoveListeners(MoveGestureDetector detector) {
+ for (MapboxMap.OnMoveListener listener : onMoveListenerList) {
+ listener.onMove(detector);
+ }
+ }
+
+ void notifyOnMoveEndListeners(MoveGestureDetector detector) {
+ for (MapboxMap.OnMoveListener listener : onMoveListenerList) {
+ listener.onMoveEnd(detector);
+ }
+ }
+
+ void notifyOnRotateBeginListeners(RotateGestureDetector detector) {
+ for (MapboxMap.OnRotateListener listener : onRotateListenerList) {
+ listener.onRotateBegin(detector);
+ }
+ }
+
+ void notifyOnRotateListeners(RotateGestureDetector detector) {
+ for (MapboxMap.OnRotateListener listener : onRotateListenerList) {
+ listener.onRotate(detector);
+ }
+ }
+
+ void notifyOnRotateEndListeners(RotateGestureDetector detector) {
+ for (MapboxMap.OnRotateListener listener : onRotateListenerList) {
+ listener.onRotateEnd(detector);
+ }
+ }
+
+ void notifyOnScaleBeginListeners(StandardScaleGestureDetector detector) {
+ for (MapboxMap.OnScaleListener listener : onScaleListenerList) {
+ listener.onScaleBegin(detector);
+ }
+ }
+
+ void notifyOnScaleListeners(StandardScaleGestureDetector detector) {
+ for (MapboxMap.OnScaleListener listener : onScaleListenerList) {
+ listener.onScale(detector);
+ }
+ }
+
+ void notifyOnScaleEndListeners(StandardScaleGestureDetector detector) {
+ for (MapboxMap.OnScaleListener listener : onScaleListenerList) {
+ listener.onScaleEnd(detector);
+ }
+ }
+
+ void notifyOnShoveBeginListeners(ShoveGestureDetector detector) {
+ for (MapboxMap.OnShoveListener listener : onShoveListenerList) {
+ listener.onShoveBegin(detector);
+ }
+ }
+
+ void notifyOnShoveListeners(ShoveGestureDetector detector) {
+ for (MapboxMap.OnShoveListener listener : onShoveListenerList) {
+ listener.onShove(detector);
+ }
+ }
+
+ void notifyOnShoveEndListeners(ShoveGestureDetector detector) {
+ for (MapboxMap.OnShoveListener listener : onShoveListenerList) {
+ listener.onShoveEnd(detector);
+ }
+ }
+
void setOnMapClickListener(MapboxMap.OnMapClickListener onMapClickListener) {
this.onMapClickListener = onMapClickListener;
}
@@ -962,4 +971,44 @@ final class MapGestureDetector {
void removeOnScrollListener(MapboxMap.OnScrollListener onScrollListener) {
onScrollListenerList.remove(onScrollListener);
}
-}
+
+ void addOnMoveListener(MapboxMap.OnMoveListener listener) {
+ onMoveListenerList.add(listener);
+ }
+
+ void removeOnMoveListener(MapboxMap.OnMoveListener listener) {
+ onMoveListenerList.remove(listener);
+ }
+
+ void addOnRotateListener(MapboxMap.OnRotateListener listener) {
+ onRotateListenerList.add(listener);
+ }
+
+ void removeOnRotateListener(MapboxMap.OnRotateListener listener) {
+ onRotateListenerList.remove(listener);
+ }
+
+ void addOnScaleListener(MapboxMap.OnScaleListener listener) {
+ onScaleListenerList.add(listener);
+ }
+
+ void removeOnScaleListener(MapboxMap.OnScaleListener listener) {
+ onScaleListenerList.remove(listener);
+ }
+
+ void addShoveListener(MapboxMap.OnShoveListener listener) {
+ onShoveListenerList.add(listener);
+ }
+
+ void removeShoveListener(MapboxMap.OnShoveListener listener) {
+ onShoveListenerList.remove(listener);
+ }
+
+ AndroidGesturesManager getGesturesManager() {
+ return gesturesManager;
+ }
+
+ void setGesturesManager(AndroidGesturesManager gesturesManager) {
+ this.gesturesManager = gesturesManager;
+ }
+} \ No newline at end of file
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapKeyListener.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapKeyListener.java
index d1f01a30f7..9bd9499fff 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapKeyListener.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapKeyListener.java
@@ -128,7 +128,7 @@ final class MapKeyListener {
// Zoom out
PointF focalPoint = new PointF(uiSettings.getWidth() / 2, uiSettings.getHeight() / 2);
- transform.zoom(false, focalPoint);
+ transform.zoomOut(focalPoint);
return true;
default:
@@ -164,7 +164,7 @@ final class MapKeyListener {
// Zoom in
PointF focalPoint = new PointF(uiSettings.getWidth() / 2, uiSettings.getHeight() / 2);
- transform.zoom(true, focalPoint);
+ transform.zoomIn(focalPoint);
return true;
}
@@ -219,7 +219,7 @@ final class MapKeyListener {
if (currentTrackballLongPressTimeOut != null) {
// Zoom in
PointF focalPoint = new PointF(uiSettings.getWidth() / 2, uiSettings.getHeight() / 2);
- transform.zoom(true, focalPoint);
+ transform.zoomIn(focalPoint);
}
return true;
@@ -261,7 +261,7 @@ final class MapKeyListener {
if (!cancelled) {
// Zoom out
PointF pointF = new PointF(uiSettings.getWidth() / 2, uiSettings.getHeight() / 2);
- transform.zoom(false, pointF);
+ transform.zoomOut(pointF);
// Ensure the up action is not run
currentTrackballLongPressTimeOut = null;
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java
index 2e7d4c4270..90feb228ab 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java
@@ -23,6 +23,12 @@ import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.ZoomButtonsController;
+import com.mapbox.android.gestures.AndroidGesturesManager;
+import com.mapbox.android.telemetry.AppUserTurnstile;
+import com.mapbox.android.telemetry.Event;
+import com.mapbox.android.telemetry.MapEventFactory;
+import com.mapbox.android.telemetry.MapboxTelemetry;
+import com.mapbox.mapboxsdk.BuildConfig;
import com.mapbox.mapboxsdk.R;
import com.mapbox.mapboxsdk.annotations.Annotation;
import com.mapbox.mapboxsdk.annotations.MarkerViewManager;
@@ -36,10 +42,10 @@ import com.mapbox.mapboxsdk.maps.widgets.MyLocationView;
import com.mapbox.mapboxsdk.maps.widgets.MyLocationViewSettings;
import com.mapbox.mapboxsdk.net.ConnectivityReceiver;
import com.mapbox.mapboxsdk.storage.FileSource;
-import com.mapbox.services.android.telemetry.MapboxTelemetry;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
@@ -133,17 +139,7 @@ public class MapView extends FrameLayout {
setContentDescription(context.getString(R.string.mapbox_mapActionDescription));
setWillNotDraw(false);
- getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
- @Override
- public void onGlobalLayout() {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
- getViewTreeObserver().removeOnGlobalLayoutListener(this);
- } else {
- getViewTreeObserver().removeGlobalOnLayoutListener(this);
- }
- initialiseDrawingSurface(options);
- }
- });
+ getViewTreeObserver().addOnGlobalLayoutListener(new MapViewLayoutListener(this, options));
}
private void initialiseMap() {
@@ -155,7 +151,7 @@ public class MapView extends FrameLayout {
focalPointInvalidator.addListener(createFocalPointChangeListener());
// callback for registering touch listeners
- RegisterTouchListener registerTouchListener = new RegisterTouchListener();
+ GesturesManagerInteractionListener registerTouchListener = new GesturesManagerInteractionListener();
// callback for zooming in the camera
CameraZoomInvalidator zoomInvalidator = new CameraZoomInvalidator();
@@ -190,7 +186,7 @@ public class MapView extends FrameLayout {
mapCallback.attachMapboxMap(mapboxMap);
// user input
- mapGestureDetector = new MapGestureDetector(context, transform, proj, uiSettings, trackingSettings,
+ mapGestureDetector = new MapGestureDetector(context, transform, proj, uiSettings,
annotationManager, cameraChangeDispatcher);
mapKeyListener = new MapKeyListener(transform, trackingSettings, uiSettings);
@@ -285,7 +281,12 @@ public class MapView extends FrameLayout {
@UiThread
public void onCreate(@Nullable Bundle savedInstanceState) {
if (savedInstanceState == null) {
- MapboxTelemetry.getInstance().pushEvent(MapboxEventWrapper.buildMapLoadEvent());
+ MapboxTelemetry telemetry = Events.obtainTelemetry();
+ AppUserTurnstile turnstileEvent = new AppUserTurnstile(BuildConfig.MAPBOX_SDK_IDENTIFIER,
+ BuildConfig.MAPBOX_SDK_VERSION);
+ telemetry.push(turnstileEvent);
+ MapEventFactory mapEventFactory = new MapEventFactory();
+ telemetry.push(mapEventFactory.createMapLoadEvent(Event.Type.MAP_LOAD));
} else if (savedInstanceState.getBoolean(MapboxConstants.STATE_HAS_SAVED_STATE)) {
this.savedInstanceState = savedInstanceState;
}
@@ -304,7 +305,7 @@ public class MapView extends FrameLayout {
addView(textureView, 0);
} else {
- GLSurfaceView glSurfaceView = (GLSurfaceView) findViewById(R.id.surfaceView);
+ GLSurfaceView glSurfaceView = new GLSurfaceView(getContext());
glSurfaceView.setZOrderMediaOverlay(mapboxMapOptions.getRenderSurfaceOnTop());
mapRenderer = new GLSurfaceViewMapRenderer(getContext(), glSurfaceView, options.getLocalIdeographFontFamily()) {
@Override
@@ -314,7 +315,7 @@ public class MapView extends FrameLayout {
}
};
- glSurfaceView.setVisibility(View.VISIBLE);
+ addView(glSurfaceView, 0);
}
nativeMapView = new NativeMapView(this, mapRenderer);
@@ -389,6 +390,7 @@ public class MapView extends FrameLayout {
public void onStop() {
if (mapboxMap != null) {
// map was destroyed before it was started
+ mapGestureDetector.cancelAnimators();
mapboxMap.onStop();
}
@@ -486,7 +488,9 @@ public class MapView extends FrameLayout {
*/
@UiThread
public void onLowMemory() {
- nativeMapView.onLowMemory();
+ if (nativeMapView != null) {
+ nativeMapView.onLowMemory();
+ }
}
/**
@@ -879,6 +883,30 @@ public class MapView extends FrameLayout {
void onMapChanged(@MapChange int change);
}
+ private static class MapViewLayoutListener implements ViewTreeObserver.OnGlobalLayoutListener {
+
+ private WeakReference<MapView> mapViewWeakReference;
+ private MapboxMapOptions options;
+
+ MapViewLayoutListener(MapView mapView, MapboxMapOptions options) {
+ this.mapViewWeakReference = new WeakReference<>(mapView);
+ this.options = options;
+ }
+
+ @Override
+ public void onGlobalLayout() {
+ MapView mapView = mapViewWeakReference.get();
+ if (mapView != null) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+ mapView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
+ } else {
+ mapView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
+ }
+ mapView.initialiseDrawingSurface(options);
+ }
+ }
+ }
+
private class FocalPointInvalidator implements FocalPointChangeListener {
private final List<FocalPointChangeListener> focalPointChangeListeners = new ArrayList<>();
@@ -896,7 +924,7 @@ public class MapView extends FrameLayout {
}
}
- private class RegisterTouchListener implements MapboxMap.OnRegisterTouchListener {
+ private class GesturesManagerInteractionListener implements MapboxMap.OnGesturesManagerInteractionListener {
@Override
public void onSetMapClickListener(MapboxMap.OnMapClickListener listener) {
@@ -957,6 +985,56 @@ public class MapView extends FrameLayout {
public void onRemoveFlingListener(MapboxMap.OnFlingListener listener) {
mapGestureDetector.removeOnFlingListener(listener);
}
+
+ @Override
+ public void onAddMoveListener(MapboxMap.OnMoveListener listener) {
+ mapGestureDetector.addOnMoveListener(listener);
+ }
+
+ @Override
+ public void onRemoveMoveListener(MapboxMap.OnMoveListener listener) {
+ mapGestureDetector.removeOnMoveListener(listener);
+ }
+
+ @Override
+ public void onAddRotateListener(MapboxMap.OnRotateListener listener) {
+ mapGestureDetector.addOnRotateListener(listener);
+ }
+
+ @Override
+ public void onRemoveRotateListener(MapboxMap.OnRotateListener listener) {
+ mapGestureDetector.removeOnRotateListener(listener);
+ }
+
+ @Override
+ public void onAddScaleListener(MapboxMap.OnScaleListener listener) {
+ mapGestureDetector.addOnScaleListener(listener);
+ }
+
+ @Override
+ public void onRemoveScaleListener(MapboxMap.OnScaleListener listener) {
+ mapGestureDetector.removeOnScaleListener(listener);
+ }
+
+ @Override
+ public void onAddShoveListener(MapboxMap.OnShoveListener listener) {
+ mapGestureDetector.addShoveListener(listener);
+ }
+
+ @Override
+ public void onRemoveShoveListener(MapboxMap.OnShoveListener listener) {
+ mapGestureDetector.removeShoveListener(listener);
+ }
+
+ @Override
+ public AndroidGesturesManager getGesturesManager() {
+ return mapGestureDetector.getGesturesManager();
+ }
+
+ @Override
+ public void setGesturesManager(AndroidGesturesManager gesturesManager) {
+ mapGestureDetector.setGesturesManager(gesturesManager);
+ }
}
private static class MapZoomControllerListener implements ZoomButtonsController.OnZoomListener {
@@ -994,11 +1072,13 @@ public class MapView extends FrameLayout {
}
private void onZoom(boolean zoomIn, @Nullable PointF focalPoint) {
- if (focalPoint != null) {
- transform.zoom(zoomIn, focalPoint);
+ if (focalPoint == null) {
+ focalPoint = new PointF(mapWidth / 2, mapHeight / 2);
+ }
+ if (zoomIn) {
+ transform.zoomIn(focalPoint);
} else {
- PointF centerPoint = new PointF(mapWidth / 2, mapHeight / 2);
- transform.zoom(zoomIn, centerPoint);
+ transform.zoomOut(focalPoint);
}
}
}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxEventWrapper.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxEventWrapper.java
deleted file mode 100644
index 6730278d79..0000000000
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxEventWrapper.java
+++ /dev/null
@@ -1,57 +0,0 @@
-package com.mapbox.mapboxsdk.maps;
-
-import android.location.Location;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-
-import com.mapbox.mapboxsdk.constants.MapboxConstants;
-import com.mapbox.services.android.telemetry.MapboxEvent;
-
-import java.util.Hashtable;
-
-/**
- * Wrapper class for MapboxEvent
- * <p>
- * Provides facility methods to use Transform and handle the case that the zoom, required for a telemetry event,
- * isn't available yet.
- * </p>
- */
-class MapboxEventWrapper {
-
- @Nullable
- static Hashtable<String, Object> buildMapClickEvent(
- @NonNull Location location, @NonNull String gestureId, Transform transform) {
- try {
- double mapZoom = transform.getZoom();
- if (mapZoom >= MapboxConstants.MINIMUM_ZOOM && mapZoom <= MapboxConstants.MAXIMUM_ZOOM) {
- // validate zoom #8057
- return MapboxEvent.buildMapClickEvent(location, gestureId, transform.getZoom());
- }
- } catch (NullPointerException exception) {
- // Map/Transform is not ready yet #8650
- // returning null is valid, event is ignored.
- }
- return null;
- }
-
- @Nullable
- static Hashtable<String, Object> buildMapDragEndEvent(
- @NonNull Location location, Transform transform) {
- try {
- double mapZoom = transform.getZoom();
- if (mapZoom >= MapboxConstants.MINIMUM_ZOOM && mapZoom <= MapboxConstants.MAXIMUM_ZOOM) {
- // validate zoom #8057
- return MapboxEvent.buildMapDragEndEvent(location, transform.getZoom());
- }
- } catch (NullPointerException exception) {
- // Map/Transform is not ready yet #8650
- // returning null is valid, event is ignored.
- }
- return null;
- }
-
- @Nullable
- static Hashtable<String, Object> buildMapLoadEvent() {
- return MapboxEvent.buildMapLoadEvent();
- }
-}
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 509e784e58..cbd3842a02 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
@@ -16,6 +16,14 @@ import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
+import com.mapbox.android.core.location.LocationEngine;
+import com.mapbox.android.gestures.AndroidGesturesManager;
+import com.mapbox.android.gestures.MoveGestureDetector;
+import com.mapbox.android.gestures.RotateGestureDetector;
+import com.mapbox.android.gestures.ShoveGestureDetector;
+import com.mapbox.android.gestures.StandardScaleGestureDetector;
+import com.mapbox.geojson.Feature;
+import com.mapbox.geojson.Geometry;
import com.mapbox.mapboxsdk.annotations.Annotation;
import com.mapbox.mapboxsdk.annotations.BaseMarkerOptions;
import com.mapbox.mapboxsdk.annotations.BaseMarkerViewOptions;
@@ -41,9 +49,6 @@ import com.mapbox.mapboxsdk.style.layers.Filter;
import com.mapbox.mapboxsdk.style.layers.Layer;
import com.mapbox.mapboxsdk.style.light.Light;
import com.mapbox.mapboxsdk.style.sources.Source;
-import com.mapbox.services.android.telemetry.location.LocationEngine;
-import com.mapbox.services.commons.geojson.Feature;
-import com.mapbox.services.commons.geojson.Geometry;
import java.lang.reflect.ParameterizedType;
import java.util.HashMap;
@@ -73,13 +78,13 @@ public final class MapboxMap {
private final MyLocationViewSettings myLocationViewSettings;
private final CameraChangeDispatcher cameraChangeDispatcher;
- private final OnRegisterTouchListener onRegisterTouchListener;
+ private final OnGesturesManagerInteractionListener onGesturesManagerInteractionListener;
private MapboxMap.OnFpsChangedListener onFpsChangedListener;
private PointF focalPoint;
MapboxMap(NativeMapView map, Transform transform, UiSettings ui, TrackingSettings tracking,
- MyLocationViewSettings myLocationView, Projection projection, OnRegisterTouchListener listener,
+ MyLocationViewSettings myLocationView, Projection projection, OnGesturesManagerInteractionListener listener,
AnnotationManager annotations, CameraChangeDispatcher cameraChangeDispatcher) {
this.nativeMapView = map;
this.uiSettings = ui;
@@ -88,7 +93,7 @@ public final class MapboxMap {
this.myLocationViewSettings = myLocationView;
this.annotationManager = annotations.bind(this);
this.transform = transform;
- this.onRegisterTouchListener = listener;
+ this.onGesturesManagerInteractionListener = listener;
this.cameraChangeDispatcher = cameraChangeDispatcher;
}
@@ -1882,12 +1887,11 @@ public final class MapboxMap {
*
* @param listener The callback that's invoked when the map is scrolled.
* To unset the callback, use null.
- *
* @deprecated Use {@link #addOnScrollListener(OnScrollListener)} instead.
*/
@Deprecated
public void setOnScrollListener(@Nullable OnScrollListener listener) {
- onRegisterTouchListener.onSetScrollListener(listener);
+ onGesturesManagerInteractionListener.onSetScrollListener(listener);
}
/**
@@ -1895,10 +1899,9 @@ public final class MapboxMap {
*
* @param listener The callback that's invoked when the map is scrolled.
* To unset the callback, use null.
- *
*/
public void addOnScrollListener(@Nullable OnScrollListener listener) {
- onRegisterTouchListener.onAddScrollListener(listener);
+ onGesturesManagerInteractionListener.onAddScrollListener(listener);
}
/**
@@ -1906,10 +1909,9 @@ public final class MapboxMap {
*
* @param listener The callback that's invoked when the map is scrolled.
* To unset the callback, use null.
- *
*/
public void removeOnScrollListener(@Nullable OnScrollListener listener) {
- onRegisterTouchListener.onRemoveScrollListener(listener);
+ onGesturesManagerInteractionListener.onRemoveScrollListener(listener);
}
/**
@@ -1917,12 +1919,11 @@ public final class MapboxMap {
*
* @param listener The callback that's invoked when the map is flinged.
* To unset the callback, use null.
- *
* @deprecated Use {@link #addOnFlingListener(OnFlingListener)} instead.
*/
@Deprecated
public void setOnFlingListener(@Nullable OnFlingListener listener) {
- onRegisterTouchListener.onSetFlingListener(listener);
+ onGesturesManagerInteractionListener.onSetFlingListener(listener);
}
/**
@@ -1932,7 +1933,7 @@ public final class MapboxMap {
* To unset the callback, use null.
*/
public void addOnFlingListener(@Nullable OnFlingListener listener) {
- onRegisterTouchListener.onAddFlingListener(listener);
+ onGesturesManagerInteractionListener.onAddFlingListener(listener);
}
/**
@@ -1942,7 +1943,98 @@ public final class MapboxMap {
* To unset the callback, use null.
*/
public void removeOnFlingListener(@Nullable OnFlingListener listener) {
- onRegisterTouchListener.onRemoveFlingListener(listener);
+ onGesturesManagerInteractionListener.onRemoveFlingListener(listener);
+ }
+
+ /**
+ * Adds a callback that's invoked when the map is moved.
+ *
+ * @param listener The callback that's invoked when the map is moved.
+ */
+ public void addOnMoveListener(OnMoveListener listener) {
+ onGesturesManagerInteractionListener.onAddMoveListener(listener);
+ }
+
+ /**
+ * Removes a callback that's invoked when the map is moved.
+ *
+ * @param listener The callback that's invoked when the map is moved.
+ */
+ public void removeOnMoveListener(OnMoveListener listener) {
+ onGesturesManagerInteractionListener.onRemoveMoveListener(listener);
+ }
+
+ /**
+ * Adds a callback that's invoked when the map is rotated.
+ *
+ * @param listener The callback that's invoked when the map is rotated.
+ */
+ public void addOnRotateListener(OnRotateListener listener) {
+ onGesturesManagerInteractionListener.onAddRotateListener(listener);
+ }
+
+ /**
+ * Removes a callback that's invoked when the map is rotated.
+ *
+ * @param listener The callback that's invoked when the map is rotated.
+ */
+ public void removeOnRotateListener(OnRotateListener listener) {
+ onGesturesManagerInteractionListener.onRemoveRotateListener(listener);
+ }
+
+ /**
+ * Adds a callback that's invoked when the map is scaled.
+ *
+ * @param listener The callback that's invoked when the map is scaled.
+ */
+ public void addOnScaleListener(OnScaleListener listener) {
+ onGesturesManagerInteractionListener.onAddScaleListener(listener);
+ }
+
+ /**
+ * Removes a callback that's invoked when the map is scaled.
+ *
+ * @param listener The callback that's invoked when the map is scaled.
+ */
+ public void removeOnScaleListener(OnScaleListener listener) {
+ onGesturesManagerInteractionListener.onRemoveScaleListener(listener);
+ }
+
+ /**
+ * Adds a callback that's invoked when the map is tilted.
+ *
+ * @param listener The callback that's invoked when the map is tilted.
+ */
+ public void addOnShoveListener(OnShoveListener listener) {
+ onGesturesManagerInteractionListener.onAddShoveListener(listener);
+ }
+
+ /**
+ * Remove a callback that's invoked when the map is tilted.
+ *
+ * @param listener The callback that's invoked when the map is tilted.
+ */
+ public void removeOnShoveListener(OnShoveListener listener) {
+ onGesturesManagerInteractionListener.onRemoveShoveListener(listener);
+ }
+
+ /**
+ * Sets a custom {@link AndroidGesturesManager} to handle {@link android.view.MotionEvent}s registered by the map.
+ *
+ * @param androidGesturesManager Gestures manager that interprets gestures based on the motion events.
+ * @see <a href="https://github.com/mapbox/mapbox-gestures-android">mapbox-gestures-android library</a>
+ */
+ public void setGesturesManager(AndroidGesturesManager androidGesturesManager) {
+ onGesturesManagerInteractionListener.setGesturesManager(androidGesturesManager);
+ }
+
+ /**
+ * Get current {@link AndroidGesturesManager} that handles {@link android.view.MotionEvent}s registered by the map.
+ *
+ * @return Current gestures manager.
+ */
+ public AndroidGesturesManager getGesturesManager() {
+ return onGesturesManagerInteractionListener.getGesturesManager();
}
/**
@@ -1950,12 +2042,11 @@ public final class MapboxMap {
*
* @param listener The callback that's invoked when the user clicks on the map view.
* To unset the callback, use null.
- *
* @deprecated Use {@link #addOnMapClickListener(OnMapClickListener)} instead.
*/
@Deprecated
public void setOnMapClickListener(@Nullable OnMapClickListener listener) {
- onRegisterTouchListener.onSetMapClickListener(listener);
+ onGesturesManagerInteractionListener.onSetMapClickListener(listener);
}
/**
@@ -1965,7 +2056,7 @@ public final class MapboxMap {
* To unset the callback, use null.
*/
public void addOnMapClickListener(@Nullable OnMapClickListener listener) {
- onRegisterTouchListener.onAddMapClickListener(listener);
+ onGesturesManagerInteractionListener.onAddMapClickListener(listener);
}
/**
@@ -1975,7 +2066,7 @@ public final class MapboxMap {
* To unset the callback, use null.
*/
public void removeOnMapClickListener(@Nullable OnMapClickListener listener) {
- onRegisterTouchListener.onRemoveMapClickListener(listener);
+ onGesturesManagerInteractionListener.onRemoveMapClickListener(listener);
}
/**
@@ -1983,12 +2074,11 @@ public final class MapboxMap {
*
* @param listener The callback that's invoked when the user long clicks on the map view.
* To unset the callback, use null.
- *
* @deprecated Use {@link #addOnMapLongClickListener(OnMapLongClickListener)} instead.
*/
@Deprecated
public void setOnMapLongClickListener(@Nullable OnMapLongClickListener listener) {
- onRegisterTouchListener.onSetMapLongClickListener(listener);
+ onGesturesManagerInteractionListener.onSetMapLongClickListener(listener);
}
/**
@@ -1998,7 +2088,7 @@ public final class MapboxMap {
* To unset the callback, use null.
*/
public void addOnMapLongClickListener(@Nullable OnMapLongClickListener listener) {
- onRegisterTouchListener.onAddMapLongClickListener(listener);
+ onGesturesManagerInteractionListener.onAddMapLongClickListener(listener);
}
/**
@@ -2008,7 +2098,7 @@ public final class MapboxMap {
* To unset the callback, use null.
*/
public void removeOnMapLongClickListener(@Nullable OnMapLongClickListener listener) {
- onRegisterTouchListener.onRemoveMapLongClickListener(listener);
+ onGesturesManagerInteractionListener.onRemoveMapLongClickListener(listener);
}
/**
@@ -2267,7 +2357,9 @@ public final class MapboxMap {
* Interface definition for a callback to be invoked when the map is scrolled.
*
* @see MapboxMap#setOnScrollListener(OnScrollListener)
+ * @deprecated Use {@link OnMoveListener} instead.
*/
+ @Deprecated
public interface OnScrollListener {
/**
* Called when the map is scrolled.
@@ -2276,6 +2368,58 @@ public final class MapboxMap {
}
/**
+ * Interface definition for a callback to be invoked when the map is moved.
+ *
+ * @see MapboxMap#addOnMoveListener(OnMoveListener)
+ */
+ public interface OnMoveListener {
+ void onMoveBegin(MoveGestureDetector detector);
+
+ void onMove(MoveGestureDetector detector);
+
+ void onMoveEnd(MoveGestureDetector detector);
+ }
+
+ /**
+ * Interface definition for a callback to be invoked when the map is rotated.
+ *
+ * @see MapboxMap#addOnRotateListener(OnRotateListener)
+ */
+ public interface OnRotateListener {
+ void onRotateBegin(RotateGestureDetector detector);
+
+ void onRotate(RotateGestureDetector detector);
+
+ void onRotateEnd(RotateGestureDetector detector);
+ }
+
+ /**
+ * Interface definition for a callback to be invoked when the map is scaled.
+ *
+ * @see MapboxMap#addOnScaleListener(OnScaleListener)
+ */
+ public interface OnScaleListener {
+ void onScaleBegin(StandardScaleGestureDetector detector);
+
+ void onScale(StandardScaleGestureDetector detector);
+
+ void onScaleEnd(StandardScaleGestureDetector detector);
+ }
+
+ /**
+ * Interface definition for a callback to be invoked when the map is tilted.
+ *
+ * @see MapboxMap#addOnShoveListener(OnShoveListener)
+ */
+ public interface OnShoveListener {
+ void onShoveBegin(ShoveGestureDetector detector);
+
+ void onShove(ShoveGestureDetector detector);
+
+ void onShoveEnd(ShoveGestureDetector detector);
+ }
+
+ /**
* Interface definition for a callback to be invoked when the camera changes position.
*
* @deprecated Replaced by {@link MapboxMap.OnCameraMoveStartedListener}, {@link MapboxMap.OnCameraMoveListener} and
@@ -2377,7 +2521,7 @@ public final class MapboxMap {
* Interface definition for a callback to be invoked when a user registers an listener that is
* related to touch and click events.
*/
- interface OnRegisterTouchListener {
+ interface OnGesturesManagerInteractionListener {
void onSetMapClickListener(OnMapClickListener listener);
void onAddMapClickListener(OnMapClickListener listener);
@@ -2401,6 +2545,26 @@ public final class MapboxMap {
void onAddFlingListener(OnFlingListener listener);
void onRemoveFlingListener(OnFlingListener listener);
+
+ void onAddMoveListener(OnMoveListener listener);
+
+ void onRemoveMoveListener(OnMoveListener listener);
+
+ void onAddRotateListener(OnRotateListener listener);
+
+ void onRemoveRotateListener(OnRotateListener listener);
+
+ void onAddScaleListener(OnScaleListener listener);
+
+ void onRemoveScaleListener(OnScaleListener listener);
+
+ void onAddShoveListener(OnShoveListener listener);
+
+ void onRemoveShoveListener(OnShoveListener listener);
+
+ AndroidGesturesManager getGesturesManager();
+
+ void setGesturesManager(AndroidGesturesManager gesturesManager);
}
/**
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 f1635c898f..785b045779 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
@@ -11,6 +11,8 @@ import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.DisplayMetrics;
+import com.mapbox.geojson.Feature;
+import com.mapbox.geojson.Geometry;
import com.mapbox.mapboxsdk.LibraryLoader;
import com.mapbox.mapboxsdk.annotations.Icon;
import com.mapbox.mapboxsdk.annotations.Marker;
@@ -29,8 +31,6 @@ import com.mapbox.mapboxsdk.style.light.Light;
import com.mapbox.mapboxsdk.style.sources.CannotAddSourceException;
import com.mapbox.mapboxsdk.style.sources.Source;
import com.mapbox.mapboxsdk.utils.BitmapUtils;
-import com.mapbox.services.commons.geojson.Feature;
-import com.mapbox.services.commons.geojson.Geometry;
import java.nio.ByteBuffer;
import java.util.ArrayList;
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Projection.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Projection.java
index 16c73b1ca5..ae559189ad 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Projection.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Projection.java
@@ -104,11 +104,12 @@ public class Projection {
LatLng bottomLeft = fromScreenLocation(new PointF(left, bottom));
return new VisibleRegion(topLeft, topRight, bottomLeft, bottomRight,
- LatLngBounds.from(
- topRight.getLatitude(),
- topRight.getLongitude(),
- bottomLeft.getLatitude(),
- bottomLeft.getLongitude())
+ new LatLngBounds.Builder()
+ .include(topRight)
+ .include(bottomLeft)
+ .include(bottomRight)
+ .include(topLeft)
+ .build()
);
}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/TrackingSettings.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/TrackingSettings.java
index 6eacbbaeaf..3743096824 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/TrackingSettings.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/TrackingSettings.java
@@ -12,9 +12,9 @@ import com.mapbox.mapboxsdk.constants.MapboxConstants;
import com.mapbox.mapboxsdk.constants.MyBearingTracking;
import com.mapbox.mapboxsdk.constants.MyLocationTracking;
import com.mapbox.mapboxsdk.maps.widgets.MyLocationView;
-import com.mapbox.services.android.telemetry.location.LocationEngine;
-import com.mapbox.services.android.telemetry.location.LocationEngineListener;
-import com.mapbox.services.android.telemetry.permissions.PermissionsManager;
+import com.mapbox.android.core.location.LocationEngine;
+import com.mapbox.android.core.location.LocationEngineListener;
+import com.mapbox.android.core.permissions.PermissionsManager;
import timber.log.Timber;
@@ -374,7 +374,7 @@ public final class TrackingSettings {
}
private void setMyLocationEnabled(boolean locationEnabled, boolean isCustomLocationSource) {
- if (!PermissionsManager.areLocationPermissionsGranted(myLocationView.getContext())) {
+ if (locationEnabled && !PermissionsManager.areLocationPermissionsGranted(myLocationView.getContext())) {
Timber.e("Could not activate user location tracking: "
+ "user did not accept the permission or permissions were not requested.");
return;
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Transform.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Transform.java
index 84a601039f..43c943a16f 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Transform.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Transform.java
@@ -205,6 +205,8 @@ final class Transform implements MapView.OnMapChangedListener {
// cancel ongoing transitions
mapView.cancelTransitions();
+
+ cameraChangeDispatcher.onCameraIdle();
}
@UiThread
@@ -235,39 +237,37 @@ final class Transform implements MapView.OnMapChangedListener {
return mapView.getZoom();
}
- void zoom(boolean zoomIn, @NonNull PointF focalPoint) {
+ void zoomIn(@NonNull PointF focalPoint) {
CameraPosition cameraPosition = invalidateCameraPosition();
if (cameraPosition != null) {
- int newZoom = (int) Math.round(cameraPosition.zoom + (zoomIn ? 1 : -1));
- setZoom(newZoom, focalPoint, MapboxConstants.ANIMATION_DURATION, false);
- } else {
- // we are not transforming, notify about being idle
- cameraChangeDispatcher.onCameraIdle();
+ int newZoom = (int) Math.round(cameraPosition.zoom + 1);
+ setZoom(newZoom, focalPoint, MapboxConstants.ANIMATION_DURATION);
}
}
- void zoom(double zoomAddition, @NonNull PointF focalPoint, long duration) {
+ void zoomOut(@NonNull PointF focalPoint) {
CameraPosition cameraPosition = invalidateCameraPosition();
if (cameraPosition != null) {
- int newZoom = (int) Math.round(cameraPosition.zoom + zoomAddition);
- setZoom(newZoom, focalPoint, duration, false);
- } else {
- // we are not transforming, notify about being idle
- cameraChangeDispatcher.onCameraIdle();
+ int newZoom = (int) Math.round(cameraPosition.zoom - 1);
+ setZoom(newZoom, focalPoint, MapboxConstants.ANIMATION_DURATION);
}
}
+ void zoomBy(double zoomAddition, @NonNull PointF focalPoint) {
+ setZoom(mapView.getZoom() + zoomAddition, focalPoint, 0);
+ }
+
void setZoom(double zoom, @NonNull PointF focalPoint) {
- setZoom(zoom, focalPoint, 0, false);
+ setZoom(zoom, focalPoint, 0);
}
- void setZoom(double zoom, @NonNull PointF focalPoint, long duration, final boolean isAnimator) {
+ void setZoom(double zoom, @NonNull PointF focalPoint, long duration) {
if (mapView != null) {
mapView.addOnMapChangedListener(new MapView.OnMapChangedListener() {
@Override
public void onMapChanged(int change) {
if (change == MapView.REGION_DID_CHANGE_ANIMATED) {
- if (!isAnimator) {
+ if (duration > 0) {
cameraChangeDispatcher.onCameraIdle();
}
mapView.removeOnMapChangedListener(this);
@@ -361,10 +361,6 @@ final class Transform implements MapView.OnMapChangedListener {
}
}
- void zoomBy(double z, float x, float y) {
- mapView.setZoom(mapView.getZoom() + z, new PointF(x, y), 0);
- }
-
void moveBy(double offsetX, double offsetY, long duration) {
if (duration > 0) {
mapView.addOnMapChangedListener(new MapView.OnMapChangedListener() {
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/renderer/textureview/TextureViewRenderThread.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/renderer/textureview/TextureViewRenderThread.java
index c34833e9ce..1e76ffe3fb 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/renderer/textureview/TextureViewRenderThread.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/renderer/textureview/TextureViewRenderThread.java
@@ -4,7 +4,6 @@ import android.graphics.SurfaceTexture;
import android.support.annotation.NonNull;
import android.support.annotation.UiThread;
import android.view.TextureView;
-
import com.mapbox.mapboxsdk.maps.renderer.egl.EGLConfigChooser;
import java.lang.ref.WeakReference;
@@ -219,13 +218,6 @@ class TextureViewRenderThread extends Thread implements TextureView.SurfaceTextu
break;
}
- // Check if the size has changed
- if (sizeChanged) {
- recreateSurface = true;
- sizeChanged = false;
- break;
- }
-
// Reset the request render flag now, so we can catch new requests
// while rendering
requestRender = false;
@@ -273,6 +265,12 @@ class TextureViewRenderThread extends Thread implements TextureView.SurfaceTextu
continue;
}
+ if (sizeChanged) {
+ mapRenderer.onSurfaceChanged(gl, w, h);
+ sizeChanged = false;
+ continue;
+ }
+
// Don't continue without a surface
if (eglHolder.eglSurface == EGL10.EGL_NO_SURFACE) {
continue;
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/CompassView.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/CompassView.java
index 1e604c9bef..45f72af1c5 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/CompassView.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/CompassView.java
@@ -6,10 +6,10 @@ import android.support.annotation.NonNull;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.ViewPropertyAnimatorCompat;
import android.support.v4.view.ViewPropertyAnimatorListenerAdapter;
+import android.support.v7.widget.AppCompatImageView;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.ImageView;
import com.mapbox.mapboxsdk.maps.MapboxMap;
@@ -22,7 +22,7 @@ import com.mapbox.mapboxsdk.maps.MapboxMap;
* use {@link com.mapbox.mapboxsdk.maps.UiSettings}.
* </p>
*/
-public final class CompassView extends ImageView implements Runnable {
+public final class CompassView extends AppCompatImageView implements Runnable {
public static final long TIME_WAIT_IDLE = 500;
public static final long TIME_MAP_NORTH_ANIMATION = 150;
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/MyLocationView.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/MyLocationView.java
index 4f27e0ada8..3f37da99d5 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/MyLocationView.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/MyLocationView.java
@@ -35,9 +35,9 @@ import com.mapbox.mapboxsdk.constants.MyLocationTracking;
import com.mapbox.mapboxsdk.geometry.LatLng;
import com.mapbox.mapboxsdk.maps.MapboxMap;
import com.mapbox.mapboxsdk.maps.Projection;
-import com.mapbox.services.android.telemetry.location.LocationEngine;
-import com.mapbox.services.android.telemetry.location.LocationEngineListener;
-import com.mapbox.services.android.telemetry.location.LocationEnginePriority;
+import com.mapbox.android.core.location.LocationEngine;
+import com.mapbox.android.core.location.LocationEngineListener;
+import com.mapbox.android.core.location.LocationEnginePriority;
import java.lang.ref.WeakReference;
@@ -356,7 +356,9 @@ public class MyLocationView extends View {
foregroundDrawable.draw(canvas);
}
} else if (foregroundBearingDrawable != null && foregroundBounds != null) {
- if (myBearingTrackingMode == MyBearingTracking.GPS || compassListener.isSensorAvailable()) {
+ if (myBearingTrackingMode == MyBearingTracking.GPS
+ || myBearingTrackingMode == MyBearingTracking.GPS_NORTH_FACING
+ || compassListener.isSensorAvailable()) {
foregroundBearingDrawable.draw(canvas);
} else {
// We are tracking MyBearingTracking.COMPASS, but sensor is not available.
@@ -383,7 +385,8 @@ public class MyLocationView extends View {
public void setBearing(double bearing) {
this.bearing = bearing;
if (myLocationTrackingMode == MyLocationTracking.TRACKING_NONE) {
- if (myBearingTrackingMode == MyBearingTracking.GPS) {
+ if (myBearingTrackingMode == MyBearingTracking.GPS
+ || myBearingTrackingMode == MyBearingTracking.GPS_NORTH_FACING) {
if (location != null) {
setCompass(location.getBearing() - bearing);
}
@@ -519,7 +522,8 @@ public class MyLocationView extends View {
}
private void toggleGps(boolean enableGps) {
- toggleGps(enableGps, mapboxMap != null && mapboxMap.getTrackingSettings().isCustomLocationSource());
+ toggleGps(enableGps, mapboxMap != null
+ && mapboxMap.getTrackingSettings().isCustomLocationSource());
}
/**
@@ -580,7 +584,8 @@ public class MyLocationView extends View {
this.location = location;
myLocationBehavior.updateLatLng(location);
- if (mapboxMap != null && myBearingTrackingMode == MyBearingTracking.GPS
+ if (mapboxMap != null && (myBearingTrackingMode == MyBearingTracking.GPS
+ || myBearingTrackingMode == MyBearingTracking.GPS_NORTH_FACING)
&& myLocationTrackingMode == MyLocationTracking.TRACKING_NONE) {
setBearing(mapboxMap.getCameraPosition().bearing);
}
@@ -616,7 +621,8 @@ public class MyLocationView extends View {
compassListener.onResume();
} else {
compassListener.onPause();
- if (myLocationTrackingMode == MyLocationTracking.TRACKING_FOLLOW) {
+ if (myLocationTrackingMode == MyLocationTracking.TRACKING_FOLLOW
+ && myBearingTrackingMode == MyBearingTracking.GPS) {
// always face north
setCompass(0);
} else {
@@ -1017,6 +1023,13 @@ public class MyLocationView extends View {
setCompass(0, COMPASS_UPDATE_RATE_MS);
}
+ if (myBearingTrackingMode == MyBearingTracking.GPS_NORTH_FACING) {
+ builder.bearing(0);
+ if (location.hasBearing()) {
+ setCompass(location.getBearing(), COMPASS_UPDATE_RATE_MS);
+ }
+ }
+
// accuracy
updateAccuracy(location);
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 f0cb8d973a..929e4b4279 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
@@ -6,6 +6,8 @@ import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.os.Environment;
import android.support.annotation.NonNull;
+import android.support.annotation.UiThread;
+
import com.mapbox.mapboxsdk.Mapbox;
import com.mapbox.mapboxsdk.constants.MapboxConstants;
import timber.log.Timber;
@@ -43,6 +45,7 @@ public class FileSource {
* @param context the context to derive the cache path from
* @return the single instance of FileSource
*/
+ @UiThread
public static synchronized FileSource getInstance(Context context) {
if (INSTANCE == null) {
String cachePath = getCachePath(context);
@@ -122,6 +125,8 @@ public class FileSource {
initialize(Mapbox.getAccessToken(), cachePath, assetManager);
}
+ public native boolean isActivated();
+
public native void activate();
public native void deactivate();
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/HeatmapLayer.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/HeatmapLayer.java
new file mode 100644
index 0000000000..6b8fd65def
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/HeatmapLayer.java
@@ -0,0 +1,221 @@
+// This file is generated. Edit android/platform/scripts/generate-style-code.js, then run `make android-style-code`.
+
+package com.mapbox.mapboxsdk.style.layers;
+
+import android.support.annotation.ColorInt;
+import android.support.annotation.NonNull;
+import android.support.annotation.UiThread;
+
+import static com.mapbox.mapboxsdk.utils.ColorUtils.rgbaToColor;
+
+import com.mapbox.mapboxsdk.style.layers.TransitionOptions;
+
+/**
+ * A heatmap.
+ *
+ * @see <a href="https://www.mapbox.com/mapbox-gl-style-spec/#layers-heatmap">The online documentation</a>
+ */
+@UiThread
+public class HeatmapLayer extends Layer {
+
+ /**
+ * Creates a HeatmapLayer.
+ *
+ * @param nativePtr pointer used by core
+ */
+ public HeatmapLayer(long nativePtr) {
+ super(nativePtr);
+ }
+
+ /**
+ * Creates a HeatmapLayer.
+ *
+ * @param layerId the id of the layer
+ * @param sourceId the id of the source
+ */
+ public HeatmapLayer(String layerId, String sourceId) {
+ initialize(layerId, sourceId);
+ }
+
+ protected native void initialize(String layerId, String sourceId);
+
+ /**
+ * Set the source layer.
+ *
+ * @param sourceLayer the source layer to set
+ */
+ public void setSourceLayer(String sourceLayer) {
+ nativeSetSourceLayer(sourceLayer);
+ }
+
+ /**
+ * Set the source Layer.
+ *
+ * @param sourceLayer the source layer to set
+ * @return This
+ */
+ public HeatmapLayer withSourceLayer(String sourceLayer) {
+ setSourceLayer(sourceLayer);
+ return this;
+ }
+
+ /**
+ * Get the source layer.
+ *
+ * @return sourceLayer the source layer to get
+ */
+ public String getSourceLayer() {
+ return nativeGetSourceLayer();
+ }
+
+ /**
+ * Set a single filter.
+ *
+ * @param filter the filter to set
+ */
+ public void setFilter(Filter.Statement filter) {
+ nativeSetFilter(filter.toArray());
+ }
+
+ /**
+ * Set a single filter.
+ *
+ * @param filter the filter to set
+ * @return This
+ */
+ public HeatmapLayer withFilter(Filter.Statement filter) {
+ setFilter(filter);
+ return this;
+ }
+
+ /**
+ * Set a property or properties.
+ *
+ * @param properties the var-args properties
+ * @return This
+ */
+ public HeatmapLayer withProperties(@NonNull PropertyValue<?>... properties) {
+ setProperties(properties);
+ return this;
+ }
+
+ // Property getters
+
+ /**
+ * Get the HeatmapRadius property
+ *
+ * @return property wrapper value around Float
+ */
+ @SuppressWarnings("unchecked")
+ public PropertyValue<Float> getHeatmapRadius() {
+ return (PropertyValue<Float>) new PropertyValue("heatmap-radius", nativeGetHeatmapRadius());
+ }
+
+ /**
+ * Get the HeatmapRadius property transition options
+ *
+ * @return transition options for Float
+ */
+ public TransitionOptions getHeatmapRadiusTransition() {
+ return nativeGetHeatmapRadiusTransition();
+ }
+
+ /**
+ * Set the HeatmapRadius property transition options
+ *
+ * @param options transition options for Float
+ */
+ public void setHeatmapRadiusTransition(TransitionOptions options) {
+ nativeSetHeatmapRadiusTransition(options.getDuration(), options.getDelay());
+ }
+
+ /**
+ * Get the HeatmapWeight property
+ *
+ * @return property wrapper value around Float
+ */
+ @SuppressWarnings("unchecked")
+ public PropertyValue<Float> getHeatmapWeight() {
+ return (PropertyValue<Float>) new PropertyValue("heatmap-weight", nativeGetHeatmapWeight());
+ }
+
+ /**
+ * Get the HeatmapIntensity property
+ *
+ * @return property wrapper value around Float
+ */
+ @SuppressWarnings("unchecked")
+ public PropertyValue<Float> getHeatmapIntensity() {
+ return (PropertyValue<Float>) new PropertyValue("heatmap-intensity", nativeGetHeatmapIntensity());
+ }
+
+ /**
+ * Get the HeatmapIntensity property transition options
+ *
+ * @return transition options for Float
+ */
+ public TransitionOptions getHeatmapIntensityTransition() {
+ return nativeGetHeatmapIntensityTransition();
+ }
+
+ /**
+ * Set the HeatmapIntensity property transition options
+ *
+ * @param options transition options for Float
+ */
+ public void setHeatmapIntensityTransition(TransitionOptions options) {
+ nativeSetHeatmapIntensityTransition(options.getDuration(), options.getDelay());
+ }
+
+ /**
+ * Get the HeatmapOpacity property
+ *
+ * @return property wrapper value around Float
+ */
+ @SuppressWarnings("unchecked")
+ public PropertyValue<Float> getHeatmapOpacity() {
+ return (PropertyValue<Float>) new PropertyValue("heatmap-opacity", nativeGetHeatmapOpacity());
+ }
+
+ /**
+ * Get the HeatmapOpacity property transition options
+ *
+ * @return transition options for Float
+ */
+ public TransitionOptions getHeatmapOpacityTransition() {
+ return nativeGetHeatmapOpacityTransition();
+ }
+
+ /**
+ * Set the HeatmapOpacity property transition options
+ *
+ * @param options transition options for Float
+ */
+ public void setHeatmapOpacityTransition(TransitionOptions options) {
+ nativeSetHeatmapOpacityTransition(options.getDuration(), options.getDelay());
+ }
+
+ private native Object nativeGetHeatmapRadius();
+
+ private native TransitionOptions nativeGetHeatmapRadiusTransition();
+
+ private native void nativeSetHeatmapRadiusTransition(long duration, long delay);
+
+ private native Object nativeGetHeatmapWeight();
+
+ private native Object nativeGetHeatmapIntensity();
+
+ private native TransitionOptions nativeGetHeatmapIntensityTransition();
+
+ private native void nativeSetHeatmapIntensityTransition(long duration, long delay);
+
+ private native Object nativeGetHeatmapOpacity();
+
+ private native TransitionOptions nativeGetHeatmapOpacityTransition();
+
+ private native void nativeSetHeatmapOpacityTransition(long duration, long delay);
+
+ @Override
+ protected native void finalize() throws Throwable;
+
+}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/HillshadeLayer.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/HillshadeLayer.java
index dc65cb5081..7e3d3a7779 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/HillshadeLayer.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/HillshadeLayer.java
@@ -11,7 +11,7 @@ import static com.mapbox.mapboxsdk.utils.ColorUtils.rgbaToColor;
import com.mapbox.mapboxsdk.style.layers.TransitionOptions;
/**
- * Client-side hillshading visualization based on DEM data. Currently, the implementation only supports Mapbox Terrain RGB tiles
+ * Client-side hillshading visualization based on DEM data. Currently, the implementation only supports Mapbox Terrain RGB and Mapzen Terrarium tiles.
*
* @see <a href="https://www.mapbox.com/mapbox-gl-style-spec/#layers-hillshade">The online documentation</a>
*/
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/PropertyFactory.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/PropertyFactory.java
index 6e644c5591..18ee05e63b 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/PropertyFactory.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/PropertyFactory.java
@@ -1516,6 +1516,138 @@ public class PropertyFactory {
}
/**
+ * Radius of influence of one heatmap point in density-independent pixels. Increasing the value makes the heatmap smoother, but less detailed.
+ *
+ * @param value a Float value
+ * @return property wrapper around Float
+ */
+ public static PropertyValue<Float> heatmapRadius(Float value) {
+ return new PaintPropertyValue<>("heatmap-radius", value);
+ }
+
+ /**
+ * Radius of influence of one heatmap point in density-independent pixels. Increasing the value makes the heatmap smoother, but less detailed.
+ *
+ * @param expression an expression statement
+ * @return property wrapper around an expression statement
+ */
+ public static PropertyValue<Expression> heatmapRadius(Expression expression) {
+ return new PaintPropertyValue<>("heatmap-radius", expression);
+ }
+
+
+ /**
+ * Radius of influence of one heatmap point in density-independent pixels. Increasing the value makes the heatmap smoother, but less detailed.
+ *
+ * @param <T> the function input type
+ * @param function a wrapper function for Float
+ * @return property wrapper around a Float function
+ */
+ @Deprecated
+ public static <T> PropertyValue<Function<T, Float>> heatmapRadius(Function<T, Float> function) {
+ return new PaintPropertyValue<>("heatmap-radius", function);
+ }
+
+ /**
+ * A measure of how much an individual point contributes to the heatmap. A value of 10 would be equivalent to having 10 points of weight 1 in the same spot. Especially useful when combined with clustering.
+ *
+ * @param value a Float value
+ * @return property wrapper around Float
+ */
+ public static PropertyValue<Float> heatmapWeight(Float value) {
+ return new PaintPropertyValue<>("heatmap-weight", value);
+ }
+
+ /**
+ * A measure of how much an individual point contributes to the heatmap. A value of 10 would be equivalent to having 10 points of weight 1 in the same spot. Especially useful when combined with clustering.
+ *
+ * @param expression an expression statement
+ * @return property wrapper around an expression statement
+ */
+ public static PropertyValue<Expression> heatmapWeight(Expression expression) {
+ return new PaintPropertyValue<>("heatmap-weight", expression);
+ }
+
+
+ /**
+ * A measure of how much an individual point contributes to the heatmap. A value of 10 would be equivalent to having 10 points of weight 1 in the same spot. Especially useful when combined with clustering.
+ *
+ * @param <T> the function input type
+ * @param function a wrapper function for Float
+ * @return property wrapper around a Float function
+ */
+ @Deprecated
+ public static <T> PropertyValue<Function<T, Float>> heatmapWeight(Function<T, Float> function) {
+ return new PaintPropertyValue<>("heatmap-weight", function);
+ }
+
+ /**
+ * Similar to {@link PropertyFactory#heatmapWeight} but controls the intensity of the heatmap globally. Primarily used for adjusting the heatmap based on zoom level.
+ *
+ * @param value a Float value
+ * @return property wrapper around Float
+ */
+ public static PropertyValue<Float> heatmapIntensity(Float value) {
+ return new PaintPropertyValue<>("heatmap-intensity", value);
+ }
+
+ /**
+ * Similar to {@link PropertyFactory#heatmapWeight} but controls the intensity of the heatmap globally. Primarily used for adjusting the heatmap based on zoom level.
+ *
+ * @param expression an expression statement
+ * @return property wrapper around an expression statement
+ */
+ public static PropertyValue<Expression> heatmapIntensity(Expression expression) {
+ return new PaintPropertyValue<>("heatmap-intensity", expression);
+ }
+
+
+ /**
+ * Similar to {@link PropertyFactory#heatmapWeight} but controls the intensity of the heatmap globally. Primarily used for adjusting the heatmap based on zoom level.
+ *
+ * @param <Z> the zoom parameter type
+ * @param function a wrapper {@link CameraFunction} for Float
+ * @return property wrapper around a Float function
+ */
+ @Deprecated
+ public static <Z extends Number> PropertyValue<CameraFunction<Z, Float>> heatmapIntensity(CameraFunction<Z, Float> function) {
+ return new PaintPropertyValue<>("heatmap-intensity", function);
+ }
+
+ /**
+ * The global opacity at which the heatmap layer will be drawn.
+ *
+ * @param value a Float value
+ * @return property wrapper around Float
+ */
+ public static PropertyValue<Float> heatmapOpacity(Float value) {
+ return new PaintPropertyValue<>("heatmap-opacity", value);
+ }
+
+ /**
+ * The global opacity at which the heatmap layer will be drawn.
+ *
+ * @param expression an expression statement
+ * @return property wrapper around an expression statement
+ */
+ public static PropertyValue<Expression> heatmapOpacity(Expression expression) {
+ return new PaintPropertyValue<>("heatmap-opacity", expression);
+ }
+
+
+ /**
+ * The global opacity at which the heatmap layer will be drawn.
+ *
+ * @param <Z> the zoom parameter type
+ * @param function a wrapper {@link CameraFunction} for Float
+ * @return property wrapper around a Float function
+ */
+ @Deprecated
+ public static <Z extends Number> PropertyValue<CameraFunction<Z, Float>> heatmapOpacity(CameraFunction<Z, Float> function) {
+ return new PaintPropertyValue<>("heatmap-opacity", function);
+ }
+
+ /**
* The opacity of the entire fill extrusion layer. This is rendered on a per-layer, not per-feature, basis, and data-driven styling is not available.
*
* @param value a Float value
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/CustomGeometrySource.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/CustomGeometrySource.java
index 62f1719ddf..1b0999ae2e 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/CustomGeometrySource.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/CustomGeometrySource.java
@@ -5,10 +5,10 @@ import android.support.annotation.Nullable;
import android.support.annotation.UiThread;
import android.support.annotation.WorkerThread;
+import com.mapbox.geojson.Feature;
+import com.mapbox.geojson.FeatureCollection;
import com.mapbox.mapboxsdk.geometry.LatLngBounds;
import com.mapbox.mapboxsdk.style.layers.Filter;
-import com.mapbox.services.commons.geojson.Feature;
-import com.mapbox.services.commons.geojson.FeatureCollection;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
@@ -37,18 +37,18 @@ public class CustomGeometrySource extends Source {
* @param provider The tile provider that returns geometry data for this source.
*/
public CustomGeometrySource(String id, GeometryTileProvider provider) {
- this(id, provider, new GeoJsonOptions());
+ this(id, provider, new CustomGeometrySourceOptions());
}
/**
- * Create a CustomGeometrySource with non-default GeoJsonOptions.
+ * Create a CustomGeometrySource with non-default CustomGeometrySourceOptions.
* <p>Supported options are minZoom, maxZoom, buffer, and tolerance.</p>
*
* @param id The source id.
* @param provider The tile provider that returns geometry data for this source.
- * @param options GeoJsonOptions.
+ * @param options CustomGeometrySourceOptions.
*/
- public CustomGeometrySource(String id, GeometryTileProvider provider, GeoJsonOptions options) {
+ public CustomGeometrySource(String id, GeometryTileProvider provider, CustomGeometrySourceOptions options) {
this.provider = provider;
executor = Executors.newFixedThreadPool(4);
initialize(id, options);
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/CustomGeometrySourceOptions.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/CustomGeometrySourceOptions.java
new file mode 100644
index 0000000000..4ada38c238
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/CustomGeometrySourceOptions.java
@@ -0,0 +1,31 @@
+package com.mapbox.mapboxsdk.style.sources;
+
+/**
+ * Builder class for composing CustomGeometrySource objects.
+ */
+public class CustomGeometrySourceOptions extends GeoJsonOptions {
+
+ /**
+ * If the data includes wrapped coordinates, setting this to true unwraps the coordinates.
+ *
+ * @param wrap defaults to false
+ * @return the current instance for chaining
+ */
+ public CustomGeometrySourceOptions withWrap(boolean wrap) {
+ this.put("wrap", wrap);
+ return this;
+ }
+
+ /**
+ * If the data includes geometry outside the tile boundaries, setting this to true clips the geometry
+ * to the tile boundaries.
+ *
+ * @param clip defaults to false
+ * @return the current instance for chaining
+ */
+ public CustomGeometrySourceOptions withClip(boolean clip) {
+ this.put("clip", clip);
+ return this;
+ }
+
+}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/GeoJsonOptions.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/GeoJsonOptions.java
index 81f7255b86..79cde7429c 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/GeoJsonOptions.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/GeoJsonOptions.java
@@ -13,7 +13,7 @@ public class GeoJsonOptions extends HashMap<String, Object> {
/**
* Maximum zoom level at which to create vector tiles (higher means greater detail at high zoom levels).
*
- * @param maxZoom the maximum zoom - Defaults to 18.
+ * @param minZoom the maximum zoom - Defaults to 18.
* @return the current instance for chaining
*/
public GeoJsonOptions withMinZoom(int minZoom) {
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/GeoJsonSource.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/GeoJsonSource.java
index 10ecb945ad..5c740554cd 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/GeoJsonSource.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/GeoJsonSource.java
@@ -4,10 +4,10 @@ import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.UiThread;
+import com.mapbox.geojson.Feature;
+import com.mapbox.geojson.FeatureCollection;
+import com.mapbox.geojson.Geometry;
import com.mapbox.mapboxsdk.style.layers.Filter;
-import com.mapbox.services.commons.geojson.Feature;
-import com.mapbox.services.commons.geojson.FeatureCollection;
-import com.mapbox.services.commons.geojson.Geometry;
import java.net.URL;
import java.util.ArrayList;
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/GeometryTileProvider.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/GeometryTileProvider.java
index 3f1eb315d3..17e7f0f5e4 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/GeometryTileProvider.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/GeometryTileProvider.java
@@ -2,8 +2,8 @@ package com.mapbox.mapboxsdk.style.sources;
import android.support.annotation.WorkerThread;
+import com.mapbox.geojson.FeatureCollection;
import com.mapbox.mapboxsdk.geometry.LatLngBounds;
-import com.mapbox.services.commons.geojson.FeatureCollection;
/**
* Interface that defines methods for working with {@link CustomGeometrySource}.
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/ImageSource.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/ImageSource.java
index 84e5e96fa4..b7679b5a16 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/ImageSource.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/ImageSource.java
@@ -124,6 +124,15 @@ public class ImageSource extends Source {
return nativeGetUrl();
}
+ /**
+ * Updates the latitude and longitude of the four corners of the image
+ *
+ * @param latLngQuad latitude and longitude of the four corners of the image
+ */
+ public void setCoordinates(LatLngQuad latLngQuad) {
+ nativeSetCoordinates(latLngQuad);
+ }
+
protected native void initialize(String layerId, LatLngQuad payload);
protected native void nativeSetUrl(String url);
@@ -132,6 +141,8 @@ public class ImageSource extends Source {
protected native void nativeSetImage(Bitmap bitmap);
+ protected native void nativeSetCoordinates(LatLngQuad latLngQuad);
+
@Override
protected native void finalize() throws Throwable;
}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/VectorSource.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/VectorSource.java
index 9b59cf8967..62b08a90ed 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/VectorSource.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/VectorSource.java
@@ -5,8 +5,8 @@ import android.support.annotation.Nullable;
import android.support.annotation.Size;
import android.support.annotation.UiThread;
+import com.mapbox.geojson.Feature;
import com.mapbox.mapboxsdk.style.layers.Filter;
-import com.mapbox.services.commons.geojson.Feature;
import java.net.URL;
import java.util.ArrayList;
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/utils/MathUtils.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/utils/MathUtils.java
new file mode 100644
index 0000000000..0c90e4b244
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/utils/MathUtils.java
@@ -0,0 +1,49 @@
+package com.mapbox.mapboxsdk.utils;
+
+// TODO Remove this class if we finally include it within MAS 3.x (GeoJSON)
+public class MathUtils {
+
+ /**
+ * Test a value in specified range, returning minimum if it's below, and maximum if it's above
+ *
+ * @param value Value to test
+ * @param min Minimum value of range
+ * @param max Maximum value of range
+ * @return value if it's between min and max, min if it's below, max if it's above
+ */
+ public static double clamp(double value, double min, double max) {
+ return Math.max(min, Math.min(max, value));
+ }
+
+ /**
+ * Test a value in specified range, returning minimum if it's below, and maximum if it's above
+ *
+ * @param value Value to test
+ * @param min Minimum value of range
+ * @param max Maximum value of range
+ * @return value if it's between min and max, min if it's below, max if it's above
+ */
+ public static float clamp(float value, float min, float max) {
+ return Math.max(min, Math.min(max, value));
+ }
+
+ /**
+ * Constrains value to the given range (including min, excluding max) via modular arithmetic.
+ * <p>
+ * Same formula as used in Core GL (wrap.hpp)
+ * std::fmod((std::fmod((value - min), d) + d), d) + min;
+ *
+ * @param value Value to wrap
+ * @param min Minimum value
+ * @param max Maximum value
+ * @return Wrapped value
+ */
+ public static double wrap(double value, double min, double max) {
+ double delta = max - min;
+
+ double firstMod = (value - min) % delta;
+ double secondMod = (firstMod + delta) % delta;
+
+ return secondMod + min;
+ }
+}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/layout/mapbox_mapview_internal.xml b/platform/android/MapboxGLAndroidSDK/src/main/res/layout/mapbox_mapview_internal.xml
index df7ccaaca9..29ff49f47e 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/res/layout/mapbox_mapview_internal.xml
+++ b/platform/android/MapboxGLAndroidSDK/src/main/res/layout/mapbox_mapview_internal.xml
@@ -1,13 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android">
- <android.opengl.GLSurfaceView
- android:id="@+id/surfaceView"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:contentDescription="@null"
- android:visibility="gone"/>
-
<FrameLayout
android:id="@+id/markerViewContainer"
android:layout_width="match_parent"
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/values-he/strings.xml b/platform/android/MapboxGLAndroidSDK/src/main/res/values-iw/strings.xml
index 11b20f5dc5..11b20f5dc5 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/res/values-he/strings.xml
+++ b/platform/android/MapboxGLAndroidSDK/src/main/res/values-iw/strings.xml
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/values/dimens.xml b/platform/android/MapboxGLAndroidSDK/src/main/res/values/dimens.xml
index 1c6a265587..00fc05cf6d 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/res/values/dimens.xml
+++ b/platform/android/MapboxGLAndroidSDK/src/main/res/values/dimens.xml
@@ -6,4 +6,13 @@
<dimen name="mapbox_eight_dp">8dp</dimen>
<dimen name="mapbox_ninety_two_dp">92dp</dimen>
<dimen name="mapbox_my_locationview_outer_circle">18dp</dimen>
+
+ <!--Minimum scale velocity required to start animation-->
+ <dimen name="mapbox_minimum_scale_velocity">150dp</dimen>
+
+ <!--Minimum scale span delta required to execute scale gesture when rotating-->
+ <dimen name="mapbox_minimum_scale_span_when_rotating">100dp</dimen>
+
+ <!--Minimum angular velocity required to start rotation animation-->
+ <dimen name="mapbox_minimum_angular_velocity">0.025dp</dimen>
</resources>
diff --git a/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/MapboxTest.java b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/MapboxTest.java
index 7a28d846ea..d9e3ae427d 100644
--- a/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/MapboxTest.java
+++ b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/MapboxTest.java
@@ -5,7 +5,7 @@ import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import com.mapbox.mapboxsdk.exceptions.MapboxConfigurationException;
-import com.mapbox.services.android.telemetry.location.LocationEngine;
+import com.mapbox.android.core.location.LocationEngine;
import org.junit.Before;
import org.junit.Test;
diff --git a/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/geometry/LatLngBoundsTest.java b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/geometry/LatLngBoundsTest.java
index e6c1fdd0cf..f03bbdb11c 100644
--- a/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/geometry/LatLngBoundsTest.java
+++ b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/geometry/LatLngBoundsTest.java
@@ -71,6 +71,82 @@ public class LatLngBoundsTest {
}
@Test
+ public void dateLineSpanBuilder1() {
+ latLngBounds = new LatLngBounds.Builder()
+ .include(new LatLng(10, -170))
+ .include(new LatLng(-10, 170))
+ .build();
+
+ LatLngSpan latLngSpan = latLngBounds.getSpan();
+ assertEquals("LatLngSpan should be shortest distance", new LatLngSpan(20, 20),
+ latLngSpan);
+ }
+
+ @Test
+ public void dateLineSpanBuilder2() {
+ latLngBounds = new LatLngBounds.Builder()
+ .include(new LatLng(-10, -170))
+ .include(new LatLng(10, 170))
+ .build();
+
+ LatLngSpan latLngSpan = latLngBounds.getSpan();
+ assertEquals("LatLngSpan should be shortest distance", new LatLngSpan(20, 20),
+ latLngSpan);
+ }
+
+ @Test
+ public void dateLineSpanFrom1() {
+ latLngBounds = LatLngBounds.from(10, -170, -10, 170);
+ LatLngSpan latLngSpan = latLngBounds.getSpan();
+ assertEquals("LatLngSpan should be shortest distance", new LatLngSpan(20, 20),
+ latLngSpan);
+ }
+
+ @Test
+ public void dateLineSpanFrom2() {
+ latLngBounds = LatLngBounds.from(10, 170, -10, -170);
+ LatLngSpan latLngSpan = latLngBounds.getSpan();
+ assertEquals("LatLngSpan should be shortest distance", new LatLngSpan(20, 340),
+ latLngSpan);
+ }
+
+ @Test
+ public void nearDateLineCenter1() {
+ latLngBounds = LatLngBounds.from(10, -175, -10, 165);
+ LatLng center = latLngBounds.getCenter();
+ assertEquals("Center should match", new LatLng(0, 175), center);
+ }
+
+ @Test
+ public void nearDateLineCenter2() {
+ latLngBounds = LatLngBounds.from(10, -165, -10, 175);
+ LatLng center = latLngBounds.getCenter();
+ assertEquals("Center should match", new LatLng(0, -175), center);
+ }
+
+ @Test
+ public void nearDateLineCenter3() {
+ latLngBounds = LatLngBounds.from(10, -170, -10, 170);
+ LatLng center = latLngBounds.getCenter();
+ assertEquals("Center should match", new LatLng(0, -180), center);
+ }
+
+ @Test
+ public void nearDateLineCenter4() {
+ latLngBounds = LatLngBounds.from(10, -180, -10, 0);
+ LatLng center = latLngBounds.getCenter();
+ assertEquals("Center should match", new LatLng(0, 90), center);
+ }
+
+ @Test
+ public void nearDateLineCenter5() {
+ latLngBounds = LatLngBounds.from(10, 180, -10, 0);
+ LatLng center = latLngBounds.getCenter();
+ assertEquals("Center should match", new LatLng(0, 90), center);
+ }
+
+
+ @Test
public void center() {
LatLng center = latLngBounds.getCenter();
assertEquals("Center should match", new LatLng(1, 1), center);
@@ -121,6 +197,46 @@ public class LatLngBoundsTest {
}
@Test
+ public void includesOverDateline1() {
+
+ LatLngBounds latLngBounds = new LatLngBounds.Builder()
+ .include(new LatLng(10, -170))
+ .include(new LatLng(-10, -175))
+ .include(new LatLng(0, 170))
+ .build();
+
+ assertEquals("LatLngSpan should be the same",
+ new LatLngSpan(20, 20), latLngBounds.getSpan());
+ }
+
+ @Test
+ public void includesOverDateline2() {
+
+ LatLngBounds latLngBounds = new LatLngBounds.Builder()
+ .include(new LatLng(10, 170))
+ .include(new LatLng(-10, 175))
+ .include(new LatLng(0, -170))
+ .build();
+
+ assertEquals("LatLngSpan should be the same",
+ new LatLngSpan(20, 20), latLngBounds.getSpan());
+ }
+
+ @Test
+ public void includesOverDateline3() {
+
+ LatLngBounds latLngBounds = new LatLngBounds.Builder()
+ .include(new LatLng(10, 170))
+ .include(new LatLng(-10, -170))
+ .include(new LatLng(0, -180))
+ .include(new LatLng(5, 180))
+ .build();
+
+ assertEquals("LatLngSpan should be the same",
+ new LatLngSpan(20, 20), latLngBounds.getSpan());
+ }
+
+ @Test
public void containsNot() {
assertFalse("LatLng should not be included", latLngBounds.contains(new LatLng(3, 1)));
}
@@ -131,6 +247,21 @@ public class LatLngBoundsTest {
}
@Test
+ public void worldSpan() {
+ assertEquals("LatLngBounds world span should be 180, 360",
+ GeometryConstants.LATITUDE_SPAN, LatLngBounds.world().getLatitudeSpan(), DELTA);
+ assertEquals("LatLngBounds world span should be 180, 360",
+ GeometryConstants.LONGITUDE_SPAN, LatLngBounds.world().getLongitudeSpan(), DELTA);
+ }
+
+ @Test
+ public void emptySpan() {
+ LatLngBounds latLngBounds = LatLngBounds.from(GeometryConstants.MIN_LATITUDE, GeometryConstants.MAX_LONGITUDE,
+ GeometryConstants.MIN_LATITUDE, GeometryConstants.MAX_LONGITUDE);
+ assertTrue("LatLngBounds empty span", latLngBounds.isEmptySpan());
+ }
+
+ @Test
public void containsBounds() {
LatLngBounds inner = new LatLngBounds.Builder()
.include(new LatLng(-5, -5))
diff --git a/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/maps/MapTouchListenersTest.java b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/maps/MapTouchListenersTest.java
index eeb00355bd..5de55f47c9 100644
--- a/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/maps/MapTouchListenersTest.java
+++ b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/maps/MapTouchListenersTest.java
@@ -2,8 +2,13 @@ package com.mapbox.mapboxsdk.maps;
import android.graphics.PointF;
+import com.mapbox.android.gestures.MoveGestureDetector;
+import com.mapbox.android.gestures.RotateGestureDetector;
+import com.mapbox.android.gestures.ShoveGestureDetector;
+import com.mapbox.android.gestures.StandardScaleGestureDetector;
import com.mapbox.mapboxsdk.geometry.LatLng;
+import org.junit.Before;
import org.junit.Test;
import static org.mockito.Mockito.mock;
@@ -13,16 +18,23 @@ import static org.mockito.Mockito.when;
public class MapTouchListenersTest {
- @Test
- public void onMapClickListenerTest() throws Exception {
- LatLng latLng = new LatLng();
- PointF pointF = new PointF();
+ private MapGestureDetector mapGestureDetector;
+ private LatLng latLng;
+ private PointF pointF;
+
+ @Before
+ public void setUp() throws Exception {
+ latLng = new LatLng();
+ pointF = new PointF();
Projection projection = mock(Projection.class);
when(projection.fromScreenLocation(pointF)).thenReturn(latLng);
- MapGestureDetector mapGestureDetector = new MapGestureDetector(null,
- null, projection, null, null, null, null);
+ mapGestureDetector = new MapGestureDetector(null,
+ null, projection, null, null, null);
+ }
+ @Test
+ public void onMapClickListenerTest() throws Exception {
MapboxMap.OnMapClickListener listener = mock(MapboxMap.OnMapClickListener.class);
mapGestureDetector.addOnMapClickListener(listener);
mapGestureDetector.notifyOnMapClickListeners(pointF);
@@ -35,14 +47,6 @@ public class MapTouchListenersTest {
@Test
public void onMapLongClickListenerTest() throws Exception {
- LatLng latLng = new LatLng();
- PointF pointF = new PointF();
-
- Projection projection = mock(Projection.class);
- when(projection.fromScreenLocation(pointF)).thenReturn(latLng);
- MapGestureDetector mapGestureDetector = new MapGestureDetector(null,
- null, projection, null, null, null, null);
-
MapboxMap.OnMapLongClickListener listener = mock(MapboxMap.OnMapLongClickListener.class);
mapGestureDetector.addOnMapLongClickListener(listener);
mapGestureDetector.notifyOnMapLongClickListeners(pointF);
@@ -55,14 +59,6 @@ public class MapTouchListenersTest {
@Test
public void onFlingListenerTest() throws Exception {
- LatLng latLng = new LatLng();
- PointF pointF = new PointF();
-
- Projection projection = mock(Projection.class);
- when(projection.fromScreenLocation(pointF)).thenReturn(latLng);
- MapGestureDetector mapGestureDetector = new MapGestureDetector(null,
- null, projection, null, null, null, null);
-
MapboxMap.OnFlingListener listener = mock(MapboxMap.OnFlingListener.class);
mapGestureDetector.addOnFlingListener(listener);
mapGestureDetector.notifyOnFlingListeners();
@@ -75,14 +71,6 @@ public class MapTouchListenersTest {
@Test
public void onScrollListenerTest() throws Exception {
- LatLng latLng = new LatLng();
- PointF pointF = new PointF();
-
- Projection projection = mock(Projection.class);
- when(projection.fromScreenLocation(pointF)).thenReturn(latLng);
- MapGestureDetector mapGestureDetector = new MapGestureDetector(null,
- null, projection, null, null, null, null);
-
MapboxMap.OnScrollListener listener = mock(MapboxMap.OnScrollListener.class);
mapGestureDetector.addOnScrollListener(listener);
mapGestureDetector.notifyOnScrollListeners();
@@ -92,4 +80,88 @@ public class MapTouchListenersTest {
mapGestureDetector.notifyOnScrollListeners();
verify(listener, times(1)).onScroll();
}
+
+ @Test
+ public void onMoveListenerTest() throws Exception {
+ MapboxMap.OnMoveListener listener = mock(MapboxMap.OnMoveListener.class);
+ MoveGestureDetector detector = mock(MoveGestureDetector.class);
+ mapGestureDetector.addOnMoveListener(listener);
+ mapGestureDetector.notifyOnMoveBeginListeners(detector);
+ mapGestureDetector.notifyOnMoveListeners(detector);
+ mapGestureDetector.notifyOnMoveEndListeners(detector);
+ verify(listener, times(1)).onMoveBegin(detector);
+ verify(listener, times(1)).onMove(detector);
+ verify(listener, times(1)).onMoveEnd(detector);
+
+ mapGestureDetector.removeOnMoveListener(listener);
+ mapGestureDetector.notifyOnMoveBeginListeners(detector);
+ mapGestureDetector.notifyOnMoveListeners(detector);
+ mapGestureDetector.notifyOnMoveEndListeners(detector);
+ verify(listener, times(1)).onMoveBegin(detector);
+ verify(listener, times(1)).onMove(detector);
+ verify(listener, times(1)).onMoveEnd(detector);
+ }
+
+ @Test
+ public void onRotateListenerTest() throws Exception {
+ MapboxMap.OnRotateListener listener = mock(MapboxMap.OnRotateListener.class);
+ RotateGestureDetector detector = mock(RotateGestureDetector.class);
+ mapGestureDetector.addOnRotateListener(listener);
+ mapGestureDetector.notifyOnRotateBeginListeners(detector);
+ mapGestureDetector.notifyOnRotateListeners(detector);
+ mapGestureDetector.notifyOnRotateEndListeners(detector);
+ verify(listener, times(1)).onRotateBegin(detector);
+ verify(listener, times(1)).onRotate(detector);
+ verify(listener, times(1)).onRotateEnd(detector);
+
+ mapGestureDetector.removeOnRotateListener(listener);
+ mapGestureDetector.notifyOnRotateBeginListeners(detector);
+ mapGestureDetector.notifyOnRotateListeners(detector);
+ mapGestureDetector.notifyOnRotateEndListeners(detector);
+ verify(listener, times(1)).onRotateBegin(detector);
+ verify(listener, times(1)).onRotate(detector);
+ verify(listener, times(1)).onRotateEnd(detector);
+ }
+
+ @Test
+ public void onScaleListenerTest() throws Exception {
+ MapboxMap.OnScaleListener listener = mock(MapboxMap.OnScaleListener.class);
+ StandardScaleGestureDetector detector = mock(StandardScaleGestureDetector.class);
+ mapGestureDetector.addOnScaleListener(listener);
+ mapGestureDetector.notifyOnScaleBeginListeners(detector);
+ mapGestureDetector.notifyOnScaleListeners(detector);
+ mapGestureDetector.notifyOnScaleEndListeners(detector);
+ verify(listener, times(1)).onScaleBegin(detector);
+ verify(listener, times(1)).onScale(detector);
+ verify(listener, times(1)).onScaleEnd(detector);
+
+ mapGestureDetector.removeOnScaleListener(listener);
+ mapGestureDetector.notifyOnScaleBeginListeners(detector);
+ mapGestureDetector.notifyOnScaleListeners(detector);
+ mapGestureDetector.notifyOnScaleEndListeners(detector);
+ verify(listener, times(1)).onScaleBegin(detector);
+ verify(listener, times(1)).onScale(detector);
+ verify(listener, times(1)).onScaleEnd(detector);
+ }
+
+ @Test
+ public void onShoveListenerTest() throws Exception {
+ MapboxMap.OnShoveListener listener = mock(MapboxMap.OnShoveListener.class);
+ ShoveGestureDetector detector = mock(ShoveGestureDetector.class);
+ mapGestureDetector.addShoveListener(listener);
+ mapGestureDetector.notifyOnShoveBeginListeners(detector);
+ mapGestureDetector.notifyOnShoveListeners(detector);
+ mapGestureDetector.notifyOnShoveEndListeners(detector);
+ verify(listener, times(1)).onShoveBegin(detector);
+ verify(listener, times(1)).onShove(detector);
+ verify(listener, times(1)).onShoveEnd(detector);
+
+ mapGestureDetector.removeShoveListener(listener);
+ mapGestureDetector.notifyOnShoveBeginListeners(detector);
+ mapGestureDetector.notifyOnShoveListeners(detector);
+ mapGestureDetector.notifyOnShoveEndListeners(detector);
+ verify(listener, times(1)).onShoveBegin(detector);
+ verify(listener, times(1)).onShove(detector);
+ verify(listener, times(1)).onShoveEnd(detector);
+ }
}
diff --git a/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/maps/MapboxMapTest.java b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/maps/MapboxMapTest.java
index 5e9f94db28..d61947f00e 100644
--- a/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/maps/MapboxMapTest.java
+++ b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/maps/MapboxMapTest.java
@@ -23,7 +23,7 @@ public class MapboxMapTest {
mock(TrackingSettings.class),
mock(MyLocationViewSettings.class),
mock(Projection.class),
- mock(MapboxMap.OnRegisterTouchListener.class),
+ mock(MapboxMap.OnGesturesManagerInteractionListener.class),
mock(AnnotationManager.class),
mock(CameraChangeDispatcher.class));
}
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/build.gradle b/platform/android/MapboxGLAndroidSDKTestApp/build.gradle
index 3723ae2acf..6707527bf2 100644
--- a/platform/android/MapboxGLAndroidSDKTestApp/build.gradle
+++ b/platform/android/MapboxGLAndroidSDKTestApp/build.gradle
@@ -52,16 +52,17 @@ android {
}
dependencies {
- implementation(project(':MapboxGLAndroidSDK'))
- implementation(dependenciesList.mapboxJavaServices) {
- transitive = true
- }
+ api(project(':MapboxGLAndroidSDK'))
+ implementation dependenciesList.mapboxJavaServices
+
+ implementation dependenciesList.mapboxJavaTurf
implementation dependenciesList.supportAppcompatV7
implementation dependenciesList.supportRecyclerView
implementation dependenciesList.supportDesign
- implementation dependenciesList.lost
+ // implementation dependenciesList.lost
+ implementation dependenciesList.gmsLocation
implementation dependenciesList.timber
debugImplementation dependenciesList.leakCanaryDebug
releaseImplementation dependenciesList.leakCanaryRelease
@@ -71,6 +72,7 @@ dependencies {
androidTestImplementation dependenciesList.testRules
androidTestImplementation dependenciesList.testEspressoCore
androidTestImplementation dependenciesList.testEspressoIntents
+ androidTestImplementation dependenciesList.testEspressoContrib
}
apply from: "${rootDir}/gradle/gradle-make.gradle"
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/maps/OrientationTest.java b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/maps/OrientationTest.java
index 7a1fcbf5f3..89397c30eb 100644
--- a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/maps/OrientationTest.java
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/maps/OrientationTest.java
@@ -16,17 +16,17 @@ public class OrientationTest extends BaseActivityTest {
@Test
public void testChangeDeviceOrientation() {
onView(isRoot()).perform(orientationLandscape());
- waitLoop(2200);
+ waitAction(2200);
onView(isRoot()).perform(orientationPortrait());
- waitLoop(2500);
+ waitAction(2500);
onView(isRoot()).perform(orientationLandscapeReverse());
- waitLoop(500);
+ waitAction(500);
onView(isRoot()).perform(orientationPortraitReverse());
- waitLoop(1250);
+ waitAction(1250);
onView(isRoot()).perform(orientationLandscape());
- waitLoop(750);
+ waitAction(750);
onView(isRoot()).perform(orientationPortrait());
- waitLoop(950);
+ waitAction(950);
onView(isRoot()).perform(orientationLandscapeReverse());
onView(isRoot()).perform(orientationPortraitReverse());
onView(isRoot()).perform(orientationLandscape());
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/action/WaitAction.java b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/action/WaitAction.java
new file mode 100644
index 0000000000..26a3a2e4ab
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/action/WaitAction.java
@@ -0,0 +1,39 @@
+package com.mapbox.mapboxsdk.testapp.action;
+
+import android.support.test.espresso.UiController;
+import android.support.test.espresso.ViewAction;
+import android.view.View;
+
+import org.hamcrest.Matcher;
+
+import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
+
+public final class WaitAction implements ViewAction {
+
+ private static final long DEFAULT_LOOP_TIME = 375;
+ private final long loopTime;
+
+ public WaitAction() {
+ this(DEFAULT_LOOP_TIME);
+ }
+
+ public WaitAction(long loopTime) {
+ this.loopTime = loopTime;
+ }
+
+ @Override
+ public Matcher<View> getConstraints() {
+ return isDisplayed();
+ }
+
+ @Override
+ public String getDescription() {
+ return getClass().getSimpleName();
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ uiController.loopMainThreadForAtLeast(loopTime);
+ }
+}
+
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/activity/BaseActivityTest.java b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/activity/BaseActivityTest.java
index 3f32443021..6d90c20a46 100644
--- a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/activity/BaseActivityTest.java
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/activity/BaseActivityTest.java
@@ -6,18 +6,19 @@ import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.support.test.espresso.Espresso;
import android.support.test.espresso.IdlingResourceTimeoutException;
-import android.support.test.espresso.UiController;
-import android.support.test.espresso.ViewAction;
import android.support.test.rule.ActivityTestRule;
-import android.view.View;
+
import com.mapbox.mapboxsdk.maps.MapboxMap;
import com.mapbox.mapboxsdk.testapp.R;
+import com.mapbox.mapboxsdk.testapp.action.WaitAction;
import com.mapbox.mapboxsdk.testapp.utils.OnMapReadyIdlingResource;
+
import junit.framework.Assert;
-import org.hamcrest.Matcher;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
+
import timber.log.Timber;
import static android.support.test.espresso.Espresso.onView;
@@ -67,12 +68,12 @@ public abstract class BaseActivityTest {
onView(withId(id)).check(matches(isDisplayed()));
}
- protected void waitLoop() {
- waitLoop(500);
+ protected void waitAction() {
+ waitAction(500);
}
- protected void waitLoop(long waitTime) {
- onView(withId(R.id.mapView)).perform(new LoopAction(waitTime));
+ protected void waitAction(long waitTime) {
+ onView(withId(R.id.mapView)).perform(new WaitAction(waitTime));
}
static boolean isConnected(Context context) {
@@ -87,29 +88,5 @@ public abstract class BaseActivityTest {
Timber.e("@After test: unregister idle resource");
Espresso.unregisterIdlingResources(idlingResource);
}
-
- private class LoopAction implements ViewAction {
-
- private long loopTime;
-
- public LoopAction(long loopTime) {
- this.loopTime = loopTime;
- }
-
- @Override
- public Matcher<View> getConstraints() {
- return isDisplayed();
- }
-
- @Override
- public String getDescription() {
- return getClass().getSimpleName();
- }
-
- @Override
- public void perform(UiController uiController, View view) {
- uiController.loopMainThreadForAtLeast(loopTime);
- }
- }
}
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/maps/widgets/CompassViewTest.java b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/maps/widgets/CompassViewTest.java
index 2a510b4dc5..26aee2de98 100644
--- a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/maps/widgets/CompassViewTest.java
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/maps/widgets/CompassViewTest.java
@@ -62,7 +62,7 @@ public class CompassViewTest extends BaseActivityTest {
.build()
)));
onView(withId(R.id.compassView)).perform(click());
- waitLoop();
+ waitAction();
onView(withId(R.id.compassView)).check(matches(not(isDisplayed())));
invoke(mapboxMap, (uiController, mapboxMap) -> {
CameraPosition cameraPosition = mapboxMap.getCameraPosition();
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/storage/FileSourceTest.java b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/storage/FileSourceTest.java
new file mode 100644
index 0000000000..554bc988a6
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/storage/FileSourceTest.java
@@ -0,0 +1,129 @@
+package com.mapbox.mapboxsdk.testapp.storage;
+
+import android.os.Looper;
+import android.support.test.espresso.UiController;
+import android.support.test.espresso.ViewAction;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.View;
+
+import com.mapbox.mapboxsdk.storage.FileSource;
+import com.mapbox.mapboxsdk.testapp.R;
+import com.mapbox.mapboxsdk.testapp.action.WaitAction;
+import com.mapbox.mapboxsdk.testapp.activity.FeatureOverviewActivity;
+
+import org.hamcrest.Matcher;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.Espresso.pressBack;
+import static android.support.test.espresso.action.ViewActions.click;
+import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static android.support.test.espresso.matcher.ViewMatchers.isRoot;
+import static android.support.test.espresso.matcher.ViewMatchers.withId;
+import static android.support.test.espresso.matcher.ViewMatchers.withText;
+import static com.mapbox.mapboxsdk.testapp.action.OrientationChangeAction.orientationLandscape;
+import static junit.framework.TestCase.assertFalse;
+import static junit.framework.TestCase.assertTrue;
+
+@RunWith(AndroidJUnit4.class)
+public class FileSourceTest {
+
+ @Rule
+ public ActivityTestRule<FeatureOverviewActivity> rule = new ActivityTestRule<>(FeatureOverviewActivity.class);
+
+ private FileSource fileSource;
+
+ @Before
+ public void setUp() throws Exception {
+ onView(withId(R.id.recyclerView)).perform(new FileSourceCreator());
+ }
+
+ @Test
+ public void testDefault() throws Exception {
+ assertFalse("FileSource should not be active", fileSource.isActivated());
+ }
+
+ @Test
+ public void testActivateDeactivate() throws Exception {
+ assertFalse("1) FileSource should not be active", fileSource.isActivated());
+ onView(withId(R.id.recyclerView)).perform(new FileSourceActivator(true));
+ assertTrue("2) FileSource should be active", fileSource.isActivated());
+ onView(withId(R.id.recyclerView)).perform(new FileSourceActivator(false));
+ assertFalse("3) FileSource should not be active", fileSource.isActivated());
+ }
+
+ @Test
+ public void testOpenCloseMapView() throws Exception {
+ assertFalse("1) FileSource should not be active", fileSource.isActivated());
+ onView(withText("Simple Map")).perform(click());
+ onView(withId(R.id.mapView)).perform(new WaitAction());
+ assertTrue("2) FileSource should be active", fileSource.isActivated());
+ onView(withId(R.id.mapView)).perform(new WaitAction());
+ pressBack();
+ assertFalse("3) FileSource should not be active", fileSource.isActivated());
+ }
+
+ @Test
+ public void testRotateMapView() throws Exception {
+ assertFalse("1) FileSource should not be active", fileSource.isActivated());
+ onView(withText("Simple Map")).perform(click());
+ onView(withId(R.id.mapView)).perform(new WaitAction());
+ onView(isRoot()).perform(orientationLandscape());
+ onView(withId(R.id.mapView)).perform(new WaitAction());
+ assertTrue("2) FileSource should be active", fileSource.isActivated());
+ onView(withId(R.id.mapView)).perform(new WaitAction());
+ pressBack();
+ assertFalse("3) FileSource should not be active", fileSource.isActivated());
+ }
+
+ private class FileSourceCreator implements ViewAction {
+ @Override
+ public Matcher<View> getConstraints() {
+ return isDisplayed();
+ }
+
+ @Override
+ public String getDescription() {
+ return "Creates the filesource instance on the UI thread";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ assertTrue(Looper.myLooper() == Looper.getMainLooper());
+ fileSource = FileSource.getInstance(rule.getActivity());
+ }
+ }
+
+ private class FileSourceActivator implements ViewAction {
+
+ private boolean activate;
+
+ FileSourceActivator(boolean activate) {
+ this.activate = activate;
+ }
+
+ @Override
+ public Matcher<View> getConstraints() {
+ return isDisplayed();
+ }
+
+ @Override
+ public String getDescription() {
+ return "Creates the filesource instance on the UI thread";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ assertTrue(Looper.myLooper() == Looper.getMainLooper());
+ if (activate) {
+ fileSource.activate();
+ } else {
+ fileSource.deactivate();
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/GeoJsonSourceTests.java b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/GeoJsonSourceTests.java
index 5d10cfa38a..2156c96973 100644
--- a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/GeoJsonSourceTests.java
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/GeoJsonSourceTests.java
@@ -13,9 +13,9 @@ import com.mapbox.mapboxsdk.testapp.R;
import com.mapbox.mapboxsdk.testapp.activity.BaseActivityTest;
import com.mapbox.mapboxsdk.testapp.activity.style.RuntimeStyleTestActivity;
import com.mapbox.mapboxsdk.testapp.utils.ResourceUtils;
-import com.mapbox.services.commons.geojson.Feature;
-import com.mapbox.services.commons.geojson.FeatureCollection;
-import com.mapbox.services.commons.geojson.Point;
+import com.mapbox.geojson.Feature;
+import com.mapbox.geojson.FeatureCollection;
+import com.mapbox.geojson.Point;
import org.hamcrest.Matcher;
import org.junit.Test;
@@ -67,7 +67,7 @@ public class GeoJsonSourceTests extends BaseActivityTest {
@Override
public void perform(UiController uiController, View view) {
- GeoJsonSource source = new GeoJsonSource("source", Point.fromCoordinates(new double[] {0d, 0d}));
+ GeoJsonSource source = new GeoJsonSource("source", Point.fromLngLat(0d, 0d));
mapboxMap.addSource(source);
mapboxMap.addLayer(new CircleLayer("layer", source.getId()));
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/HeatmapLayerTest.java b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/HeatmapLayerTest.java
new file mode 100644
index 0000000000..364c8d2679
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/HeatmapLayerTest.java
@@ -0,0 +1,626 @@
+// This file is generated. Edit android/platform/scripts/generate-style-code.js, then run `make android-style-code`.
+
+package com.mapbox.mapboxsdk.testapp.style;
+
+import android.graphics.Color;
+import android.support.test.espresso.UiController;
+import android.support.test.runner.AndroidJUnit4;
+
+import timber.log.Timber;
+
+import com.mapbox.mapboxsdk.maps.MapboxMap;
+import com.mapbox.mapboxsdk.style.functions.CompositeFunction;
+import com.mapbox.mapboxsdk.style.functions.CameraFunction;
+import com.mapbox.mapboxsdk.style.functions.SourceFunction;
+import com.mapbox.mapboxsdk.style.functions.stops.CategoricalStops;
+import com.mapbox.mapboxsdk.style.functions.stops.ExponentialStops;
+import com.mapbox.mapboxsdk.style.functions.stops.IdentityStops;
+import com.mapbox.mapboxsdk.style.functions.stops.IntervalStops;
+import com.mapbox.mapboxsdk.style.functions.stops.Stop;
+import com.mapbox.mapboxsdk.style.functions.stops.Stops;
+import com.mapbox.mapboxsdk.style.layers.HeatmapLayer;
+import com.mapbox.mapboxsdk.testapp.action.MapboxMapAction;
+import com.mapbox.mapboxsdk.testapp.activity.BaseActivityTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static com.mapbox.mapboxsdk.style.functions.Function.*;
+import static com.mapbox.mapboxsdk.style.functions.stops.Stop.stop;
+import static com.mapbox.mapboxsdk.style.functions.stops.Stops.*;
+import static com.mapbox.mapboxsdk.testapp.action.MapboxMapAction.invoke;
+import static org.junit.Assert.*;
+import static com.mapbox.mapboxsdk.style.layers.Property.*;
+import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.*;
+
+import com.mapbox.mapboxsdk.style.layers.TransitionOptions;
+import com.mapbox.mapboxsdk.testapp.activity.espresso.EspressoTestActivity;
+
+/**
+ * Basic smoke tests for HeatmapLayer
+ */
+@RunWith(AndroidJUnit4.class)
+public class HeatmapLayerTest extends BaseActivityTest {
+
+ private HeatmapLayer layer;
+
+ @Override
+ protected Class getActivityClass() {
+ return EspressoTestActivity.class;
+ }
+
+ private void setupLayer() {
+ Timber.i("Retrieving layer");
+ invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() {
+ @Override
+ public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) {
+ if ((layer = mapboxMap.getLayerAs("my-layer")) == null) {
+ Timber.i("Adding layer");
+ layer = new HeatmapLayer("my-layer", "composite");
+ layer.setSourceLayer("composite");
+ mapboxMap.addLayer(layer);
+ // Layer reference is now stale, get new reference
+ layer = mapboxMap.getLayerAs("my-layer");
+ }
+ }
+ });
+ }
+
+ @Test
+ public void testSetVisibility() {
+ validateTestSetup();
+ setupLayer();
+ Timber.i("Visibility");
+ invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() {
+ @Override
+ public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) {
+ assertNotNull(layer);
+
+ // Get initial
+ assertEquals(layer.getVisibility().getValue(), VISIBLE);
+
+ // Set
+ layer.setProperties(visibility(NONE));
+ assertEquals(layer.getVisibility().getValue(), NONE);
+ }
+ });
+ }
+
+ @Test
+ public void testSourceLayer() {
+ validateTestSetup();
+ setupLayer();
+ Timber.i("SourceLayer");
+ invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() {
+ @Override
+ public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) {
+ assertNotNull(layer);
+
+ // Get initial
+ assertEquals(layer.getSourceLayer(), "composite");
+
+ // Set
+ final String sourceLayer = "test";
+ layer.setSourceLayer(sourceLayer);
+ assertEquals(layer.getSourceLayer(), sourceLayer);
+ }
+ });
+ }
+
+ @Test
+ public void testHeatmapRadiusTransition() {
+ validateTestSetup();
+ setupLayer();
+ Timber.i("heatmap-radiusTransitionOptions");
+ invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() {
+ @Override
+ public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) {
+ assertNotNull(layer);
+
+ // Set and Get
+ TransitionOptions options = new TransitionOptions(300, 100);
+ layer.setHeatmapRadiusTransition(options);
+ assertEquals(layer.getHeatmapRadiusTransition(), options);
+ }
+ });
+ }
+
+ @Test
+ public void testHeatmapRadiusAsConstant() {
+ validateTestSetup();
+ setupLayer();
+ Timber.i("heatmap-radius");
+ invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() {
+ @Override
+ public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) {
+ assertNotNull(layer);
+
+ // Set and Get
+ layer.setProperties(heatmapRadius(0.3f));
+ assertEquals((Float) layer.getHeatmapRadius().getValue(), (Float) 0.3f);
+ }
+ });
+ }
+
+ @Test
+ public void testHeatmapRadiusAsCameraFunction() {
+ validateTestSetup();
+ setupLayer();
+ Timber.i("heatmap-radius");
+ invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() {
+ @Override
+ public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) {
+ assertNotNull(layer);
+
+ // Set
+ layer.setProperties(
+ heatmapRadius(
+ zoom(
+ exponential(
+ stop(2, heatmapRadius(0.3f))
+ ).withBase(0.5f)
+ )
+ )
+ );
+
+ // Verify
+ assertNotNull(layer.getHeatmapRadius());
+ assertNotNull(layer.getHeatmapRadius().getFunction());
+ assertEquals(CameraFunction.class, layer.getHeatmapRadius().getFunction().getClass());
+ assertEquals(ExponentialStops.class, layer.getHeatmapRadius().getFunction().getStops().getClass());
+ assertEquals(0.5f, ((ExponentialStops) layer.getHeatmapRadius().getFunction().getStops()).getBase(), 0.001);
+ assertEquals(1, ((ExponentialStops) layer.getHeatmapRadius().getFunction().getStops()).size());
+ }
+ });
+ }
+
+ @Test
+ public void testHeatmapRadiusAsIdentitySourceFunction() {
+ validateTestSetup();
+ setupLayer();
+ Timber.i("heatmap-radius");
+ invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() {
+ @Override
+ public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) {
+ assertNotNull(layer);
+
+ // Set
+ layer.setProperties(
+ heatmapRadius(property("FeaturePropertyA", Stops.<Float>identity()))
+ );
+
+ // Verify
+ assertNotNull(layer.getHeatmapRadius());
+ assertNotNull(layer.getHeatmapRadius().getFunction());
+ assertEquals(SourceFunction.class, layer.getHeatmapRadius().getFunction().getClass());
+ assertEquals("FeaturePropertyA", ((SourceFunction) layer.getHeatmapRadius().getFunction()).getProperty());
+ assertEquals(IdentityStops.class, layer.getHeatmapRadius().getFunction().getStops().getClass());
+ }
+ });
+ }
+
+ @Test
+ public void testHeatmapRadiusAsExponentialSourceFunction() {
+ validateTestSetup();
+ setupLayer();
+ Timber.i("heatmap-radius");
+ invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() {
+ @Override
+ public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) {
+ assertNotNull(layer);
+
+ // Set
+ layer.setProperties(
+ heatmapRadius(
+ property(
+ "FeaturePropertyA",
+ exponential(
+ stop(0.3f, heatmapRadius(0.3f))
+ ).withBase(0.5f)
+ )
+ )
+ );
+
+ // Verify
+ assertNotNull(layer.getHeatmapRadius());
+ assertNotNull(layer.getHeatmapRadius().getFunction());
+ assertEquals(SourceFunction.class, layer.getHeatmapRadius().getFunction().getClass());
+ assertEquals("FeaturePropertyA", ((SourceFunction) layer.getHeatmapRadius().getFunction()).getProperty());
+ assertEquals(ExponentialStops.class, layer.getHeatmapRadius().getFunction().getStops().getClass());
+ }
+ });
+ }
+
+ @Test
+ public void testHeatmapRadiusAsCategoricalSourceFunction() {
+ validateTestSetup();
+ setupLayer();
+ Timber.i("heatmap-radius");
+ invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() {
+ @Override
+ public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) {
+ assertNotNull(layer);
+
+ // Set
+ layer.setProperties(
+ heatmapRadius(
+ property(
+ "FeaturePropertyA",
+ categorical(
+ stop(1.0f, heatmapRadius(0.3f))
+ )
+ ).withDefaultValue(heatmapRadius(0.3f))
+ )
+ );
+
+ // Verify
+ assertNotNull(layer.getHeatmapRadius());
+ assertNotNull(layer.getHeatmapRadius().getFunction());
+ assertEquals(SourceFunction.class, layer.getHeatmapRadius().getFunction().getClass());
+ assertEquals("FeaturePropertyA", ((SourceFunction) layer.getHeatmapRadius().getFunction()).getProperty());
+ assertEquals(CategoricalStops.class, layer.getHeatmapRadius().getFunction().getStops().getClass());
+ assertNotNull(((SourceFunction) layer.getHeatmapRadius().getFunction()).getDefaultValue());
+ assertNotNull(((SourceFunction) layer.getHeatmapRadius().getFunction()).getDefaultValue().getValue());
+ assertEquals(0.3f, ((SourceFunction) layer.getHeatmapRadius().getFunction()).getDefaultValue().getValue());
+ }
+ });
+
+ }
+
+ @Test
+ public void testHeatmapRadiusAsCompositeFunction() {
+ validateTestSetup();
+ setupLayer();
+ Timber.i("heatmap-radius");
+ invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() {
+ @Override
+ public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) {
+ assertNotNull(layer);
+
+ // Set
+ layer.setProperties(
+ heatmapRadius(
+ composite(
+ "FeaturePropertyA",
+ exponential(
+ stop(0, 0.3f, heatmapRadius(0.9f))
+ ).withBase(0.5f)
+ ).withDefaultValue(heatmapRadius(0.3f))
+ )
+ );
+
+ // Verify
+ assertNotNull(layer.getHeatmapRadius());
+ assertNotNull(layer.getHeatmapRadius().getFunction());
+ assertEquals(CompositeFunction.class, layer.getHeatmapRadius().getFunction().getClass());
+ assertEquals("FeaturePropertyA", ((CompositeFunction) layer.getHeatmapRadius().getFunction()).getProperty());
+ assertEquals(ExponentialStops.class, layer.getHeatmapRadius().getFunction().getStops().getClass());
+ assertEquals(1, ((ExponentialStops) layer.getHeatmapRadius().getFunction().getStops()).size());
+
+ ExponentialStops<Stop.CompositeValue<Float, Float>, Float> stops =
+ (ExponentialStops<Stop.CompositeValue<Float, Float>, Float>) layer.getHeatmapRadius().getFunction().getStops();
+ Stop<Stop.CompositeValue<Float, Float>, Float> stop = stops.iterator().next();
+ assertEquals(0f, stop.in.zoom, 0.001);
+ assertEquals(0.3f, stop.in.value, 0.001f);
+ assertEquals(0.9f, stop.out, 0.001f);
+ }
+ });
+ }
+
+ @Test
+ public void testHeatmapWeightAsConstant() {
+ validateTestSetup();
+ setupLayer();
+ Timber.i("heatmap-weight");
+ invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() {
+ @Override
+ public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) {
+ assertNotNull(layer);
+
+ // Set and Get
+ layer.setProperties(heatmapWeight(0.3f));
+ assertEquals((Float) layer.getHeatmapWeight().getValue(), (Float) 0.3f);
+ }
+ });
+ }
+
+ @Test
+ public void testHeatmapWeightAsCameraFunction() {
+ validateTestSetup();
+ setupLayer();
+ Timber.i("heatmap-weight");
+ invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() {
+ @Override
+ public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) {
+ assertNotNull(layer);
+
+ // Set
+ layer.setProperties(
+ heatmapWeight(
+ zoom(
+ exponential(
+ stop(2, heatmapWeight(0.3f))
+ ).withBase(0.5f)
+ )
+ )
+ );
+
+ // Verify
+ assertNotNull(layer.getHeatmapWeight());
+ assertNotNull(layer.getHeatmapWeight().getFunction());
+ assertEquals(CameraFunction.class, layer.getHeatmapWeight().getFunction().getClass());
+ assertEquals(ExponentialStops.class, layer.getHeatmapWeight().getFunction().getStops().getClass());
+ assertEquals(0.5f, ((ExponentialStops) layer.getHeatmapWeight().getFunction().getStops()).getBase(), 0.001);
+ assertEquals(1, ((ExponentialStops) layer.getHeatmapWeight().getFunction().getStops()).size());
+ }
+ });
+ }
+
+ @Test
+ public void testHeatmapWeightAsIdentitySourceFunction() {
+ validateTestSetup();
+ setupLayer();
+ Timber.i("heatmap-weight");
+ invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() {
+ @Override
+ public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) {
+ assertNotNull(layer);
+
+ // Set
+ layer.setProperties(
+ heatmapWeight(property("FeaturePropertyA", Stops.<Float>identity()))
+ );
+
+ // Verify
+ assertNotNull(layer.getHeatmapWeight());
+ assertNotNull(layer.getHeatmapWeight().getFunction());
+ assertEquals(SourceFunction.class, layer.getHeatmapWeight().getFunction().getClass());
+ assertEquals("FeaturePropertyA", ((SourceFunction) layer.getHeatmapWeight().getFunction()).getProperty());
+ assertEquals(IdentityStops.class, layer.getHeatmapWeight().getFunction().getStops().getClass());
+ }
+ });
+ }
+
+ @Test
+ public void testHeatmapWeightAsExponentialSourceFunction() {
+ validateTestSetup();
+ setupLayer();
+ Timber.i("heatmap-weight");
+ invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() {
+ @Override
+ public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) {
+ assertNotNull(layer);
+
+ // Set
+ layer.setProperties(
+ heatmapWeight(
+ property(
+ "FeaturePropertyA",
+ exponential(
+ stop(0.3f, heatmapWeight(0.3f))
+ ).withBase(0.5f)
+ )
+ )
+ );
+
+ // Verify
+ assertNotNull(layer.getHeatmapWeight());
+ assertNotNull(layer.getHeatmapWeight().getFunction());
+ assertEquals(SourceFunction.class, layer.getHeatmapWeight().getFunction().getClass());
+ assertEquals("FeaturePropertyA", ((SourceFunction) layer.getHeatmapWeight().getFunction()).getProperty());
+ assertEquals(ExponentialStops.class, layer.getHeatmapWeight().getFunction().getStops().getClass());
+ }
+ });
+ }
+
+ @Test
+ public void testHeatmapWeightAsCategoricalSourceFunction() {
+ validateTestSetup();
+ setupLayer();
+ Timber.i("heatmap-weight");
+ invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() {
+ @Override
+ public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) {
+ assertNotNull(layer);
+
+ // Set
+ layer.setProperties(
+ heatmapWeight(
+ property(
+ "FeaturePropertyA",
+ categorical(
+ stop(1.0f, heatmapWeight(0.3f))
+ )
+ ).withDefaultValue(heatmapWeight(0.3f))
+ )
+ );
+
+ // Verify
+ assertNotNull(layer.getHeatmapWeight());
+ assertNotNull(layer.getHeatmapWeight().getFunction());
+ assertEquals(SourceFunction.class, layer.getHeatmapWeight().getFunction().getClass());
+ assertEquals("FeaturePropertyA", ((SourceFunction) layer.getHeatmapWeight().getFunction()).getProperty());
+ assertEquals(CategoricalStops.class, layer.getHeatmapWeight().getFunction().getStops().getClass());
+ assertNotNull(((SourceFunction) layer.getHeatmapWeight().getFunction()).getDefaultValue());
+ assertNotNull(((SourceFunction) layer.getHeatmapWeight().getFunction()).getDefaultValue().getValue());
+ assertEquals(0.3f, ((SourceFunction) layer.getHeatmapWeight().getFunction()).getDefaultValue().getValue());
+ }
+ });
+
+ }
+
+ @Test
+ public void testHeatmapWeightAsCompositeFunction() {
+ validateTestSetup();
+ setupLayer();
+ Timber.i("heatmap-weight");
+ invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() {
+ @Override
+ public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) {
+ assertNotNull(layer);
+
+ // Set
+ layer.setProperties(
+ heatmapWeight(
+ composite(
+ "FeaturePropertyA",
+ exponential(
+ stop(0, 0.3f, heatmapWeight(0.9f))
+ ).withBase(0.5f)
+ ).withDefaultValue(heatmapWeight(0.3f))
+ )
+ );
+
+ // Verify
+ assertNotNull(layer.getHeatmapWeight());
+ assertNotNull(layer.getHeatmapWeight().getFunction());
+ assertEquals(CompositeFunction.class, layer.getHeatmapWeight().getFunction().getClass());
+ assertEquals("FeaturePropertyA", ((CompositeFunction) layer.getHeatmapWeight().getFunction()).getProperty());
+ assertEquals(ExponentialStops.class, layer.getHeatmapWeight().getFunction().getStops().getClass());
+ assertEquals(1, ((ExponentialStops) layer.getHeatmapWeight().getFunction().getStops()).size());
+
+ ExponentialStops<Stop.CompositeValue<Float, Float>, Float> stops =
+ (ExponentialStops<Stop.CompositeValue<Float, Float>, Float>) layer.getHeatmapWeight().getFunction().getStops();
+ Stop<Stop.CompositeValue<Float, Float>, Float> stop = stops.iterator().next();
+ assertEquals(0f, stop.in.zoom, 0.001);
+ assertEquals(0.3f, stop.in.value, 0.001f);
+ assertEquals(0.9f, stop.out, 0.001f);
+ }
+ });
+ }
+
+ @Test
+ public void testHeatmapIntensityTransition() {
+ validateTestSetup();
+ setupLayer();
+ Timber.i("heatmap-intensityTransitionOptions");
+ invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() {
+ @Override
+ public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) {
+ assertNotNull(layer);
+
+ // Set and Get
+ TransitionOptions options = new TransitionOptions(300, 100);
+ layer.setHeatmapIntensityTransition(options);
+ assertEquals(layer.getHeatmapIntensityTransition(), options);
+ }
+ });
+ }
+
+ @Test
+ public void testHeatmapIntensityAsConstant() {
+ validateTestSetup();
+ setupLayer();
+ Timber.i("heatmap-intensity");
+ invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() {
+ @Override
+ public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) {
+ assertNotNull(layer);
+
+ // Set and Get
+ layer.setProperties(heatmapIntensity(0.3f));
+ assertEquals((Float) layer.getHeatmapIntensity().getValue(), (Float) 0.3f);
+ }
+ });
+ }
+
+ @Test
+ public void testHeatmapIntensityAsCameraFunction() {
+ validateTestSetup();
+ setupLayer();
+ Timber.i("heatmap-intensity");
+ invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() {
+ @Override
+ public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) {
+ assertNotNull(layer);
+
+ // Set
+ layer.setProperties(
+ heatmapIntensity(
+ zoom(
+ exponential(
+ stop(2, heatmapIntensity(0.3f))
+ ).withBase(0.5f)
+ )
+ )
+ );
+
+ // Verify
+ assertNotNull(layer.getHeatmapIntensity());
+ assertNotNull(layer.getHeatmapIntensity().getFunction());
+ assertEquals(CameraFunction.class, layer.getHeatmapIntensity().getFunction().getClass());
+ assertEquals(ExponentialStops.class, layer.getHeatmapIntensity().getFunction().getStops().getClass());
+ assertEquals(0.5f, ((ExponentialStops) layer.getHeatmapIntensity().getFunction().getStops()).getBase(), 0.001);
+ assertEquals(1, ((ExponentialStops) layer.getHeatmapIntensity().getFunction().getStops()).size());
+ }
+ });
+ }
+
+ @Test
+ public void testHeatmapOpacityTransition() {
+ validateTestSetup();
+ setupLayer();
+ Timber.i("heatmap-opacityTransitionOptions");
+ invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() {
+ @Override
+ public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) {
+ assertNotNull(layer);
+
+ // Set and Get
+ TransitionOptions options = new TransitionOptions(300, 100);
+ layer.setHeatmapOpacityTransition(options);
+ assertEquals(layer.getHeatmapOpacityTransition(), options);
+ }
+ });
+ }
+
+ @Test
+ public void testHeatmapOpacityAsConstant() {
+ validateTestSetup();
+ setupLayer();
+ Timber.i("heatmap-opacity");
+ invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() {
+ @Override
+ public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) {
+ assertNotNull(layer);
+
+ // Set and Get
+ layer.setProperties(heatmapOpacity(0.3f));
+ assertEquals((Float) layer.getHeatmapOpacity().getValue(), (Float) 0.3f);
+ }
+ });
+ }
+
+ @Test
+ public void testHeatmapOpacityAsCameraFunction() {
+ validateTestSetup();
+ setupLayer();
+ Timber.i("heatmap-opacity");
+ invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() {
+ @Override
+ public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) {
+ assertNotNull(layer);
+
+ // Set
+ layer.setProperties(
+ heatmapOpacity(
+ zoom(
+ exponential(
+ stop(2, heatmapOpacity(0.3f))
+ ).withBase(0.5f)
+ )
+ )
+ );
+
+ // Verify
+ assertNotNull(layer.getHeatmapOpacity());
+ assertNotNull(layer.getHeatmapOpacity().getFunction());
+ assertEquals(CameraFunction.class, layer.getHeatmapOpacity().getFunction().getClass());
+ assertEquals(ExponentialStops.class, layer.getHeatmapOpacity().getFunction().getStops().getClass());
+ assertEquals(0.5f, ((ExponentialStops) layer.getHeatmapOpacity().getFunction().getStops()).getBase(), 0.001);
+ assertEquals(1, ((ExponentialStops) layer.getHeatmapOpacity().getFunction().getStops()).size());
+ }
+ });
+ }
+
+}
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml
index 89f922fb9e..9d7e21024c 100644
--- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml
@@ -69,12 +69,12 @@
android:value=".activity.FeatureOverviewActivity"/>
</activity>
<activity
- android:name=".activity.annotation.AnimatedMarkerActivity"
- android:description="@string/description_animated_marker"
- android:label="@string/activity_animated_marker">
+ android:name=".activity.annotation.AnimatedSymbolLayerActivity"
+ android:description="@string/description_animated_symbollayer"
+ android:label="@string/activity_animated_symbollayer">
<meta-data
android:name="@string/category"
- android:value="@string/category_annotation"/>
+ android:value="@string/category_style"/>
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".activity.FeatureOverviewActivity"/>
@@ -789,6 +789,17 @@
android:name="android.support.PARENT_ACTIVITY"
android:value=".activity.FeatureOverviewActivity"/>
</activity>
+ <activity
+ android:name=".activity.style.HeatmapLayerActivity"
+ android:description="@string/description_heatmaplayer"
+ android:label="@string/activity_heatmaplayer">
+ <meta-data
+ android:name="@string/category"
+ android:value="@string/category_style"/>
+ <meta-data
+ android:name="android.support.PARENT_ACTIVITY"
+ android:value=".activity.FeatureOverviewActivity"/>
+ </activity>
<!-- For Instrumentation tests -->
<activity
@@ -806,7 +817,7 @@
<!-- Configuration Settings -->
<meta-data
android:name="com.mapbox.TestEventsServer"
- android:value="https://cloudfront-staging.tilestream.net"/>
+ android:value="api-events-staging.tilestream.net"/>
<meta-data
android:name="com.mapbox.TestEventsAccessToken"
android:value="pk.eyJ1IjoiYmxzdGFnaW5nIiwiYSI6ImNpdDF3OHpoaTAwMDcyeXA5Y3Z0Nmk2dzEifQ.0IfB7v5Qbm2MGVYt8Kb8fg"/>
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/FeatureOverviewActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/FeatureOverviewActivity.java
index 95cc9687f2..0ee1f78e0e 100644
--- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/FeatureOverviewActivity.java
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/FeatureOverviewActivity.java
@@ -22,8 +22,8 @@ import com.mapbox.mapboxsdk.testapp.adapter.FeatureAdapter;
import com.mapbox.mapboxsdk.testapp.adapter.FeatureSectionAdapter;
import com.mapbox.mapboxsdk.testapp.model.activity.Feature;
import com.mapbox.mapboxsdk.testapp.utils.ItemClickSupport;
-import com.mapbox.services.android.telemetry.permissions.PermissionsListener;
-import com.mapbox.services.android.telemetry.permissions.PermissionsManager;
+import com.mapbox.android.core.permissions.PermissionsListener;
+import com.mapbox.android.core.permissions.PermissionsManager;
import java.util.ArrayList;
import java.util.Collections;
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/annotation/AnimatedMarkerActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/annotation/AnimatedMarkerActivity.java
deleted file mode 100644
index a557bb4ed4..0000000000
--- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/annotation/AnimatedMarkerActivity.java
+++ /dev/null
@@ -1,283 +0,0 @@
-package com.mapbox.mapboxsdk.testapp.activity.annotation;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ObjectAnimator;
-import android.animation.TypeEvaluator;
-import android.animation.ValueAnimator;
-import android.os.Bundle;
-import android.support.annotation.DrawableRes;
-import android.support.v4.content.res.ResourcesCompat;
-import android.support.v7.app.AppCompatActivity;
-import android.view.View;
-import android.view.animation.AccelerateDecelerateInterpolator;
-
-import com.mapbox.mapboxsdk.annotations.Icon;
-import com.mapbox.mapboxsdk.annotations.IconFactory;
-import com.mapbox.mapboxsdk.annotations.Marker;
-import com.mapbox.mapboxsdk.annotations.MarkerView;
-import com.mapbox.mapboxsdk.annotations.MarkerViewManager;
-import com.mapbox.mapboxsdk.annotations.MarkerViewOptions;
-import com.mapbox.mapboxsdk.camera.CameraPosition;
-import com.mapbox.mapboxsdk.geometry.LatLng;
-import com.mapbox.mapboxsdk.geometry.LatLngBounds;
-import com.mapbox.mapboxsdk.maps.MapView;
-import com.mapbox.mapboxsdk.maps.MapboxMap;
-import com.mapbox.mapboxsdk.testapp.R;
-import com.mapbox.mapboxsdk.testapp.utils.IconUtils;
-import com.mapbox.services.api.utils.turf.TurfMeasurement;
-import com.mapbox.services.commons.models.Position;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Random;
-
-/**
- * Test activity showcasing animating MarkerViews.
- */
-public class AnimatedMarkerActivity extends AppCompatActivity {
-
- private MapView mapView;
- private MapboxMap mapboxMap;
-
- private LatLng dupontCircle = new LatLng(38.90962, -77.04341);
-
- private Marker passengerMarker = null;
- private MarkerView carMarker = null;
-
- private Runnable animationRunnable;
-
- private List<MarkerView> markerViews = new ArrayList<>();
- private boolean stopped;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_animated_marker);
-
- mapView = (MapView) findViewById(R.id.mapView);
- mapView.onCreate(savedInstanceState);
- mapView.getMapAsync(mapboxMap -> {
- AnimatedMarkerActivity.this.mapboxMap = mapboxMap;
- setupMap();
-
- animationRunnable = () -> {
- for (int i = 0; i < 10; i++) {
- addRandomCar();
- }
- addPassenger();
- addMainCar();
- };
- mapView.post(animationRunnable);
- });
- }
-
- private void setupMap() {
- CameraPosition cameraPosition = new CameraPosition.Builder()
- .target(dupontCircle)
- .zoom(15)
- .build();
- mapboxMap.setCameraPosition(cameraPosition);
- }
-
- private void addPassenger() {
- if (isActivityStopped()) {
- return;
- }
-
- LatLng randomLatLng = getLatLngInBounds();
-
- if (passengerMarker == null) {
- Icon icon = IconUtils.drawableToIcon(this, R.drawable.ic_directions_run_black,
- ResourcesCompat.getColor(getResources(), R.color.blueAccent, getTheme()));
- passengerMarker = mapboxMap.addMarker(new MarkerViewOptions()
- .position(randomLatLng)
- .icon(icon));
- } else {
- passengerMarker.setPosition(randomLatLng);
- }
- }
-
- private void addMainCar() {
- if (isActivityStopped()) {
- return;
- }
-
- LatLng randomLatLng = getLatLngInBounds();
-
- if (carMarker == null) {
- carMarker = createCarMarker(randomLatLng, R.drawable.ic_taxi_top,
- markerView -> {
- // Make sure the car marker is selected so that it's always brought to the front (#5285)
- mapboxMap.selectMarker(carMarker);
- animateMoveToPassenger(carMarker);
- });
- markerViews.add(carMarker);
- } else {
- carMarker.setPosition(randomLatLng);
- }
- }
-
- private void animateMoveToPassenger(final MarkerView car) {
- if (isActivityStopped()) {
- return;
- }
-
- ValueAnimator animator = animateMoveMarker(car, passengerMarker.getPosition());
- animator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- addPassenger();
- animateMoveToPassenger(car);
- }
- });
- }
-
- protected void addRandomCar() {
- markerViews.add(createCarMarker(getLatLngInBounds(), R.drawable.ic_car_top,
- markerView -> randomlyMoveMarker(markerView)));
- }
-
- private void randomlyMoveMarker(final MarkerView marker) {
- if (isActivityStopped()) {
- return;
- }
-
- ValueAnimator animator = animateMoveMarker(marker, getLatLngInBounds());
-
- // Add listener to restart animation on end
- animator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- randomlyMoveMarker(marker);
- }
- });
- }
-
- private ValueAnimator animateMoveMarker(final MarkerView marker, LatLng to) {
- marker.setRotation((float) getBearing(marker.getPosition(), to));
-
- final ValueAnimator markerAnimator = ObjectAnimator.ofObject(
- marker, "position", new LatLngEvaluator(), marker.getPosition(), to);
- markerAnimator.setDuration((long) (10 * marker.getPosition().distanceTo(to)));
- markerAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
-
- // Start
- markerAnimator.start();
-
- return markerAnimator;
- }
-
- private MarkerView createCarMarker(LatLng start, @DrawableRes int carResource,
- MarkerViewManager.OnMarkerViewAddedListener listener) {
- Icon icon = IconFactory.getInstance(AnimatedMarkerActivity.this)
- .fromResource(carResource);
-
- // View Markers
- return mapboxMap.addMarker(new MarkerViewOptions()
- .position(start)
- .icon(icon), listener);
-
- // GL Markers
-// return mapboxMap.addMarker(new MarkerOptions()
-// .position(start)
-// .icon(icon));
-
- }
-
- private LatLng getLatLngInBounds() {
- LatLngBounds bounds = mapboxMap.getProjection().getVisibleRegion().latLngBounds;
- Random generator = new Random();
- double randomLat = bounds.getLatSouth() + generator.nextDouble()
- * (bounds.getLatNorth() - bounds.getLatSouth());
- double randomLon = bounds.getLonWest() + generator.nextDouble()
- * (bounds.getLonEast() - bounds.getLonWest());
- return new LatLng(randomLat, randomLon);
- }
-
- @Override
- protected void onStart() {
- super.onStart();
- mapView.onStart();
- }
-
- @Override
- protected void onResume() {
- super.onResume();
- mapView.onResume();
- }
-
- @Override
- protected void onPause() {
- super.onPause();
- mapView.onPause();
- }
-
- @Override
- protected void onStop() {
- super.onStop();
-
- stopped = true;
-
- // Stop ongoing animations, prevent memory leaks
- if (mapboxMap != null) {
- MarkerViewManager markerViewManager = mapboxMap.getMarkerViewManager();
- for (MarkerView markerView : markerViews) {
- View view = markerViewManager.getView(markerView);
- if (view != null) {
- view.animate().cancel();
- }
- }
- }
-
- // onStop
- mapView.onStop();
- mapView.removeCallbacks(animationRunnable);
- }
-
- @Override
- protected void onSaveInstanceState(Bundle outState) {
- super.onSaveInstanceState(outState);
- mapView.onSaveInstanceState(outState);
- }
-
- @Override
- protected void onDestroy() {
- super.onDestroy();
- mapView.onDestroy();
- }
-
- @Override
- public void onLowMemory() {
- super.onLowMemory();
- mapView.onLowMemory();
- }
-
- /**
- * Evaluator for LatLng pairs
- */
- private static class LatLngEvaluator implements TypeEvaluator<LatLng> {
-
- private LatLng latLng = new LatLng();
-
- @Override
- public LatLng evaluate(float fraction, LatLng startValue, LatLng endValue) {
- latLng.setLatitude(startValue.getLatitude()
- + ((endValue.getLatitude() - startValue.getLatitude()) * fraction));
- latLng.setLongitude(startValue.getLongitude()
- + ((endValue.getLongitude() - startValue.getLongitude()) * fraction));
- return latLng;
- }
- }
-
- private double getBearing(LatLng from, LatLng to) {
- return TurfMeasurement.bearing(
- Position.fromCoordinates(from.getLongitude(), from.getLatitude()),
- Position.fromCoordinates(to.getLongitude(), to.getLatitude())
- );
- }
-
- private boolean isActivityStopped() {
- return stopped;
- }
-}
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/annotation/AnimatedSymbolLayerActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/annotation/AnimatedSymbolLayerActivity.java
new file mode 100644
index 0000000000..97957720fc
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/annotation/AnimatedSymbolLayerActivity.java
@@ -0,0 +1,449 @@
+package com.mapbox.mapboxsdk.testapp.activity.annotation;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.TypeEvaluator;
+import android.animation.ValueAnimator;
+import android.graphics.drawable.BitmapDrawable;
+import android.os.Bundle;
+import android.support.v7.app.AppCompatActivity;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.LinearInterpolator;
+
+import com.google.gson.JsonObject;
+import com.mapbox.geojson.Feature;
+import com.mapbox.geojson.FeatureCollection;
+import com.mapbox.geojson.Point;
+import com.mapbox.mapboxsdk.geometry.LatLng;
+import com.mapbox.mapboxsdk.geometry.LatLngBounds;
+import com.mapbox.mapboxsdk.maps.MapView;
+import com.mapbox.mapboxsdk.maps.MapboxMap;
+import com.mapbox.mapboxsdk.style.functions.stops.Stops;
+import com.mapbox.mapboxsdk.style.layers.SymbolLayer;
+import com.mapbox.mapboxsdk.style.sources.GeoJsonSource;
+import com.mapbox.mapboxsdk.testapp.R;
+import com.mapbox.turf.TurfMeasurement;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+import static com.mapbox.mapboxsdk.style.functions.Function.property;
+import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconAllowOverlap;
+import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconIgnorePlacement;
+import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconImage;
+import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconRotate;
+
+/**
+ * Test activity showcasing animating MarkerViews.
+ */
+public class AnimatedSymbolLayerActivity extends AppCompatActivity {
+
+ private static final String PASSENGER = "passenger";
+ private static final String PASSENGER_LAYER = "passenger-layer";
+ private static final String PASSENGER_SOURCE = "passenger-source";
+ private static final String TAXI = "taxi";
+ private static final String TAXI_LAYER = "taxi-layer";
+ private static final String TAXI_SOURCE = "taxi-source";
+ private static final String RANDOM_CAR_LAYER = "random-car-layer";
+ private static final String RANDOM_CAR_SOURCE = "random-car-source";
+ private static final String RANDOM_CAR_IMAGE_ID = "random-car";
+ private static final String PROPERTY_BEARING = "bearing";
+ private static final String WATERWAY_LAYER_ID = "waterway-label";
+ private static final int DURATION_RANDOM_MAX = 1500;
+ private static final int DURATION_BASE = 3000;
+
+ private final Random random = new Random();
+
+ private MapView mapView;
+ private MapboxMap mapboxMap;
+
+ private List<Car> randomCars = new ArrayList<>();
+ private GeoJsonSource randomCarSource;
+ private Car taxi;
+ private GeoJsonSource taxiSource;
+ private LatLng passenger;
+
+ private List<Animator> animators = new ArrayList<>();
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_animated_marker);
+
+ mapView = (MapView) findViewById(R.id.mapView);
+ mapView.onCreate(savedInstanceState);
+ mapView.getMapAsync(mapboxMap -> {
+ AnimatedSymbolLayerActivity.this.mapboxMap = mapboxMap;
+ setupCars();
+ animateRandomRoutes();
+ animateTaxi();
+ });
+ }
+
+ private void setupCars() {
+ addRandomCars();
+ addPassenger();
+ addMainCar();
+ }
+
+ private void animateRandomRoutes() {
+ final Car longestDrive = getLongestDrive();
+ final Random random = new Random();
+ for (final Car car : randomCars) {
+ final boolean isLongestDrive = longestDrive.equals(car);
+ ValueAnimator valueAnimator = ValueAnimator.ofObject(new LatLngEvaluator(), car.current, car.next);
+ valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+
+ private LatLng latLng;
+
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ latLng = (LatLng) animation.getAnimatedValue();
+ car.current = latLng;
+ if (isLongestDrive) {
+ updateRandomCarSource();
+ }
+ }
+ });
+
+ if (isLongestDrive) {
+ valueAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ updateRandomDestinations();
+ animateRandomRoutes();
+ }
+ });
+ }
+
+ valueAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ super.onAnimationStart(animation);
+ car.feature.properties().addProperty("bearing", Car.getBearing(car.current, car.next));
+ }
+ });
+
+ int offset = random.nextInt(2) == 0 ? 0 : random.nextInt(1000) + 250;
+ valueAnimator.setStartDelay(offset);
+ valueAnimator.setDuration(car.duration - offset);
+ valueAnimator.setInterpolator(new LinearInterpolator());
+ valueAnimator.start();
+
+ animators.add(valueAnimator);
+ }
+ }
+
+ private void animateTaxi() {
+ ValueAnimator valueAnimator = ValueAnimator.ofObject(new LatLngEvaluator(), taxi.current, taxi.next);
+ valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+
+ private LatLng latLng;
+
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ latLng = (LatLng) animation.getAnimatedValue();
+ taxi.current = latLng;
+ updateTaxiSource();
+ }
+ });
+
+ valueAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ updatePassenger();
+ animateTaxi();
+ }
+ });
+
+ valueAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ super.onAnimationStart(animation);
+ taxi.feature.properties().addProperty("bearing", Car.getBearing(taxi.current, taxi.next));
+ }
+ });
+
+ valueAnimator.setDuration((long) (7 * taxi.current.distanceTo(taxi.next)));
+ valueAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
+ valueAnimator.start();
+
+ animators.add(valueAnimator);
+ }
+
+ private void updatePassenger() {
+ passenger = getLatLngInBounds();
+ updatePassengerSource();
+ taxi.setNext(passenger);
+ }
+
+ private void updatePassengerSource() {
+ GeoJsonSource source = mapboxMap.getSourceAs(PASSENGER_SOURCE);
+ FeatureCollection featureCollection = FeatureCollection.fromFeatures(new Feature[] {
+ Feature.fromGeometry(
+ Point.fromLngLat(
+ passenger.getLongitude(),
+ passenger.getLatitude()
+ )
+ )
+ });
+ source.setGeoJson(featureCollection);
+ }
+
+ private void updateTaxiSource() {
+ taxi.updateFeature();
+ taxiSource.setGeoJson(taxi.feature);
+ }
+
+ private void updateRandomDestinations() {
+ for (Car randomCar : randomCars) {
+ randomCar.setNext(getLatLngInBounds());
+ }
+ }
+
+ private Car getLongestDrive() {
+ Car longestDrive = null;
+ for (Car randomCar : randomCars) {
+ if (longestDrive == null) {
+ longestDrive = randomCar;
+ } else if (longestDrive.duration < randomCar.duration) {
+ longestDrive = randomCar;
+ }
+ }
+ return longestDrive;
+ }
+
+ private void updateRandomCarSource() {
+ for (Car randomCarsRoute : randomCars) {
+ randomCarsRoute.updateFeature();
+ }
+ randomCarSource.setGeoJson(featuresFromRoutes());
+ }
+
+ private FeatureCollection featuresFromRoutes() {
+ List<Feature> features = new ArrayList<>();
+ for (Car randomCarsRoute : randomCars) {
+ features.add(randomCarsRoute.feature);
+ }
+ return FeatureCollection.fromFeatures(features);
+ }
+
+ private long getDuration() {
+ return random.nextInt(DURATION_RANDOM_MAX) + DURATION_BASE;
+ }
+
+ private void addRandomCars() {
+ LatLng latLng;
+ LatLng next;
+ for (int i = 0; i < 10; i++) {
+ latLng = getLatLngInBounds();
+ next = getLatLngInBounds();
+
+ JsonObject properties = new JsonObject();
+ properties.addProperty(PROPERTY_BEARING, Car.getBearing(latLng, next));
+
+ Feature feature = Feature.fromGeometry(
+ Point.fromLngLat(
+ latLng.getLongitude(),
+ latLng.getLatitude()
+ ), properties);
+
+ randomCars.add(
+ new Car(feature, next, getDuration())
+ );
+ }
+
+ randomCarSource = new GeoJsonSource(RANDOM_CAR_SOURCE, featuresFromRoutes());
+ mapboxMap.addSource(randomCarSource);
+ mapboxMap.addImage(RANDOM_CAR_IMAGE_ID,
+ ((BitmapDrawable) getResources().getDrawable(R.drawable.ic_car_top)).getBitmap());
+
+ SymbolLayer symbolLayer = new SymbolLayer(RANDOM_CAR_LAYER, RANDOM_CAR_SOURCE);
+ symbolLayer.withProperties(
+ iconImage(RANDOM_CAR_IMAGE_ID),
+ iconAllowOverlap(true),
+ iconRotate(
+ property(
+ PROPERTY_BEARING,
+ Stops.<Float>identity()
+ )
+ ),
+ iconIgnorePlacement(true)
+ );
+
+ mapboxMap.addLayerBelow(symbolLayer, WATERWAY_LAYER_ID);
+ }
+
+ private void addPassenger() {
+ passenger = getLatLngInBounds();
+ FeatureCollection featureCollection = FeatureCollection.fromFeatures(new Feature[] {
+ Feature.fromGeometry(
+ Point.fromLngLat(
+ passenger.getLongitude(),
+ passenger.getLatitude()
+ )
+ )
+ });
+
+ mapboxMap.addImage(PASSENGER,
+ ((BitmapDrawable) getResources().getDrawable(R.drawable.icon_burned)).getBitmap());
+
+ GeoJsonSource geoJsonSource = new GeoJsonSource(PASSENGER_SOURCE, featureCollection);
+ mapboxMap.addSource(geoJsonSource);
+
+ SymbolLayer symbolLayer = new SymbolLayer(PASSENGER_LAYER, PASSENGER_SOURCE);
+ symbolLayer.withProperties(
+ iconImage(PASSENGER),
+ iconIgnorePlacement(true),
+ iconAllowOverlap(true)
+ );
+ mapboxMap.addLayerBelow(symbolLayer, RANDOM_CAR_LAYER);
+ }
+
+ private void addMainCar() {
+ LatLng latLng = getLatLngInBounds();
+ JsonObject properties = new JsonObject();
+ properties.addProperty(PROPERTY_BEARING, Car.getBearing(latLng, passenger));
+ Feature feature = Feature.fromGeometry(
+ Point.fromLngLat(
+ latLng.getLongitude(),
+ latLng.getLatitude()), properties);
+ FeatureCollection featureCollection = FeatureCollection.fromFeatures(new Feature[] {feature});
+
+ taxi = new Car(feature, passenger, getDuration());
+ mapboxMap.addImage(TAXI,
+ ((BitmapDrawable) getResources().getDrawable(R.drawable.ic_taxi_top)).getBitmap());
+ taxiSource = new GeoJsonSource(TAXI_SOURCE, featureCollection);
+ mapboxMap.addSource(taxiSource);
+
+ SymbolLayer symbolLayer = new SymbolLayer(TAXI_LAYER, TAXI_SOURCE);
+ symbolLayer.withProperties(
+ iconImage(TAXI),
+ iconRotate(
+ property(
+ PROPERTY_BEARING,
+ Stops.<Float>identity()
+ )
+ ),
+ iconAllowOverlap(true),
+ iconIgnorePlacement(true)
+
+ );
+ mapboxMap.addLayer(symbolLayer);
+ }
+
+ private LatLng getLatLngInBounds() {
+ LatLngBounds bounds = mapboxMap.getProjection().getVisibleRegion().latLngBounds;
+ Random generator = new Random();
+ double randomLat = bounds.getLatSouth() + generator.nextDouble()
+ * (bounds.getLatNorth() - bounds.getLatSouth());
+ double randomLon = bounds.getLonWest() + generator.nextDouble()
+ * (bounds.getLonEast() - bounds.getLonWest());
+ return new LatLng(randomLat, randomLon);
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ mapView.onStart();
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ mapView.onResume();
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ mapView.onPause();
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ mapView.onStop();
+ }
+
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ mapView.onSaveInstanceState(outState);
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+
+ for (Animator animator : animators) {
+ if (animator != null) {
+ animator.removeAllListeners();
+ animator.cancel();
+ }
+ }
+
+ mapView.onDestroy();
+ }
+
+ @Override
+ public void onLowMemory() {
+ super.onLowMemory();
+ mapView.onLowMemory();
+ }
+
+ /**
+ * Evaluator for LatLng pairs
+ */
+ private static class LatLngEvaluator implements TypeEvaluator<LatLng> {
+
+ private LatLng latLng = new LatLng();
+
+ @Override
+ public LatLng evaluate(float fraction, LatLng startValue, LatLng endValue) {
+ latLng.setLatitude(startValue.getLatitude()
+ + ((endValue.getLatitude() - startValue.getLatitude()) * fraction));
+ latLng.setLongitude(startValue.getLongitude()
+ + ((endValue.getLongitude() - startValue.getLongitude()) * fraction));
+ return latLng;
+ }
+ }
+
+
+ private static class Car {
+ private Feature feature;
+ private LatLng next;
+ private LatLng current;
+ private long duration;
+
+ Car(Feature feature, LatLng next, long duration) {
+ this.feature = feature;
+ Point point = ((Point) feature.geometry());
+ this.current = new LatLng(point.latitude(), point.longitude());
+ this.duration = duration;
+ this.next = next;
+ }
+
+ void setNext(LatLng next) {
+ this.next = next;
+ }
+
+ void updateFeature() {
+ feature = Feature.fromGeometry(Point.fromLngLat(
+ current.getLongitude(),
+ current.getLatitude())
+ );
+ feature.properties().addProperty("bearing", getBearing(current, next));
+ }
+
+ private static float getBearing(LatLng from, LatLng to) {
+ return (float) TurfMeasurement.bearing(
+ Point.fromLngLat(from.getLongitude(), from.getLatitude()),
+ Point.fromLngLat(to.getLongitude(), to.getLatitude())
+ );
+ }
+ }
+} \ No newline at end of file
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/feature/QueryRenderedFeaturesBoxCountActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/feature/QueryRenderedFeaturesBoxCountActivity.java
index 70d5b53428..c4ea81263b 100644
--- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/feature/QueryRenderedFeaturesBoxCountActivity.java
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/feature/QueryRenderedFeaturesBoxCountActivity.java
@@ -7,10 +7,10 @@ import android.view.View;
import android.widget.Toast;
import com.google.gson.JsonElement;
+import com.mapbox.geojson.Feature;
import com.mapbox.mapboxsdk.maps.MapView;
import com.mapbox.mapboxsdk.maps.MapboxMap;
import com.mapbox.mapboxsdk.testapp.R;
-import com.mapbox.services.commons.geojson.Feature;
import java.util.List;
import java.util.Map;
@@ -62,12 +62,12 @@ public class QueryRenderedFeaturesBoxCountActivity extends AppCompatActivity {
for (Feature feature : features) {
if (feature != null) {
Timber.i("Got feature %s with %s properties and Geometry %s",
- feature.getId(),
- feature.getProperties() != null ? feature.getProperties().entrySet().size() : "<null>",
- feature.getGeometry() != null ? feature.getGeometry().getClass().getSimpleName() : "<null>"
+ feature.id(),
+ feature.properties() != null ? feature.properties().entrySet().size() : "<null>",
+ feature.geometry() != null ? feature.geometry().getClass().getSimpleName() : "<null>"
);
- if (feature.getProperties() != null) {
- for (Map.Entry<String, JsonElement> entry : feature.getProperties().entrySet()) {
+ if (feature.properties() != null) {
+ for (Map.Entry<String, JsonElement> entry : feature.properties().entrySet()) {
Timber.i("Prop %s - %s", entry.getKey(), entry.getValue());
}
}
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/feature/QueryRenderedFeaturesBoxHighlightActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/feature/QueryRenderedFeaturesBoxHighlightActivity.java
index 8c9d056764..df608360ad 100644
--- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/feature/QueryRenderedFeaturesBoxHighlightActivity.java
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/feature/QueryRenderedFeaturesBoxHighlightActivity.java
@@ -7,14 +7,15 @@ import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Toast;
+import com.mapbox.geojson.Feature;
+import com.mapbox.geojson.FeatureCollection;
import com.mapbox.mapboxsdk.maps.MapView;
import com.mapbox.mapboxsdk.maps.MapboxMap;
import com.mapbox.mapboxsdk.style.layers.FillLayer;
import com.mapbox.mapboxsdk.style.layers.Filter;
import com.mapbox.mapboxsdk.style.sources.GeoJsonSource;
import com.mapbox.mapboxsdk.testapp.R;
-import com.mapbox.services.commons.geojson.Feature;
-import com.mapbox.services.commons.geojson.FeatureCollection;
+
import java.util.List;
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/feature/QueryRenderedFeaturesBoxSymbolCountActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/feature/QueryRenderedFeaturesBoxSymbolCountActivity.java
index 9bad5f3e62..46409d1893 100644
--- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/feature/QueryRenderedFeaturesBoxSymbolCountActivity.java
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/feature/QueryRenderedFeaturesBoxSymbolCountActivity.java
@@ -7,13 +7,13 @@ import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Toast;
+import com.mapbox.geojson.Feature;
import com.mapbox.mapboxsdk.maps.MapView;
import com.mapbox.mapboxsdk.maps.MapboxMap;
import com.mapbox.mapboxsdk.style.layers.SymbolLayer;
import com.mapbox.mapboxsdk.style.sources.GeoJsonSource;
import com.mapbox.mapboxsdk.testapp.R;
import com.mapbox.mapboxsdk.testapp.utils.ResourceUtils;
-import com.mapbox.services.commons.geojson.Feature;
import java.io.IOException;
import java.util.List;
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/feature/QueryRenderedFeaturesPropertiesActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/feature/QueryRenderedFeaturesPropertiesActivity.java
index 150b081f7f..be32718dc3 100644
--- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/feature/QueryRenderedFeaturesPropertiesActivity.java
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/feature/QueryRenderedFeaturesPropertiesActivity.java
@@ -12,12 +12,13 @@ import android.widget.LinearLayout;
import android.widget.TextView;
import com.google.gson.JsonElement;
+import com.mapbox.geojson.Feature;
import com.mapbox.mapboxsdk.annotations.BaseMarkerOptions;
import com.mapbox.mapboxsdk.annotations.Marker;
import com.mapbox.mapboxsdk.maps.MapView;
import com.mapbox.mapboxsdk.maps.MapboxMap;
import com.mapbox.mapboxsdk.testapp.R;
-import com.mapbox.services.commons.geojson.Feature;
+
import java.util.List;
import java.util.Map;
@@ -80,12 +81,12 @@ public class QueryRenderedFeaturesPropertiesActivity extends AppCompatActivity {
for (Feature feature : features) {
if (feature != null) {
Timber.i("Got feature %s with %s properties and Geometry %s",
- feature.getId(),
- feature.getProperties() != null ? feature.getProperties().entrySet().size() : "<null>",
- feature.getGeometry() != null ? feature.getGeometry().getClass().getSimpleName() : "<null>"
+ feature.id(),
+ feature.properties() != null ? feature.properties().entrySet().size() : "<null>",
+ feature.geometry() != null ? feature.geometry().getClass().getSimpleName() : "<null>"
);
- if (feature.getProperties() != null) {
- for (Map.Entry<String, JsonElement> entry : feature.getProperties().entrySet()) {
+ if (feature.properties() != null) {
+ for (Map.Entry<String, JsonElement> entry : feature.properties().entrySet()) {
Timber.i("Prop %s - %s", entry.getKey(), entry.getValue());
}
}
@@ -114,7 +115,7 @@ public class QueryRenderedFeaturesPropertiesActivity extends AppCompatActivity {
if (customMarker.features.size() > 0) {
view.addView(row(String.format("Found %s features", customMarker.features.size())));
Feature feature = customMarker.features.get(0);
- for (Map.Entry<String, JsonElement> prop : feature.getProperties().entrySet()) {
+ for (Map.Entry<String, JsonElement> prop : feature.properties().entrySet()) {
view.addView(row(String.format("%s: %s", prop.getKey(), prop.getValue())));
}
} else {
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/feature/QuerySourceFeaturesActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/feature/QuerySourceFeaturesActivity.java
index c8bef26856..14de81ab30 100644
--- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/feature/QuerySourceFeaturesActivity.java
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/feature/QuerySourceFeaturesActivity.java
@@ -5,15 +5,16 @@ import android.support.v7.app.AppCompatActivity;
import android.widget.Toast;
import com.google.gson.JsonObject;
+import com.mapbox.geojson.Feature;
+import com.mapbox.geojson.FeatureCollection;
+import com.mapbox.geojson.Point;
import com.mapbox.mapboxsdk.maps.MapView;
import com.mapbox.mapboxsdk.maps.MapboxMap;
import com.mapbox.mapboxsdk.style.layers.CircleLayer;
import com.mapbox.mapboxsdk.style.layers.Filter;
import com.mapbox.mapboxsdk.style.sources.GeoJsonSource;
import com.mapbox.mapboxsdk.testapp.R;
-import com.mapbox.services.commons.geojson.Feature;
-import com.mapbox.services.commons.geojson.FeatureCollection;
-import com.mapbox.services.commons.geojson.Point;
+
import java.util.List;
@@ -42,7 +43,7 @@ public class QuerySourceFeaturesActivity extends AppCompatActivity {
properties.addProperty("key1", "value1");
final GeoJsonSource source = new GeoJsonSource("test-source",
FeatureCollection.fromFeatures(new Feature[] {
- Feature.fromGeometry(Point.fromCoordinates(new double[] {0, 0}), properties)
+ Feature.fromGeometry(Point.fromLngLat(0, 0), properties)
}));
mapboxMap.addSource(source);
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/CustomSpriteActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/CustomSpriteActivity.java
index 8d35e659d3..30cb0a8660 100644
--- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/CustomSpriteActivity.java
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/CustomSpriteActivity.java
@@ -7,6 +7,9 @@ import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
+import com.mapbox.geojson.Feature;
+import com.mapbox.geojson.FeatureCollection;
+import com.mapbox.geojson.Point;
import com.mapbox.mapboxsdk.camera.CameraUpdateFactory;
import com.mapbox.mapboxsdk.geometry.LatLng;
import com.mapbox.mapboxsdk.maps.MapView;
@@ -15,10 +18,7 @@ import com.mapbox.mapboxsdk.style.layers.Layer;
import com.mapbox.mapboxsdk.style.layers.SymbolLayer;
import com.mapbox.mapboxsdk.style.sources.GeoJsonSource;
import com.mapbox.mapboxsdk.testapp.R;
-import com.mapbox.services.commons.geojson.Feature;
-import com.mapbox.services.commons.geojson.FeatureCollection;
-import com.mapbox.services.commons.geojson.Point;
-import com.mapbox.services.commons.models.Position;
+
import timber.log.Timber;
@@ -58,7 +58,7 @@ public class CustomSpriteActivity extends AppCompatActivity {
mapboxMap.addImage(CUSTOM_ICON, BitmapFactory.decodeResource(getResources(), R.drawable.ic_car_top));
// Add a source with a geojson point
- point = Point.fromCoordinates(Position.fromCoordinates(13.400972d, 52.519003d));
+ point = Point.fromLngLat(13.400972d, 52.519003d);
source = new GeoJsonSource(
"point",
FeatureCollection.fromFeatures(new Feature[] {Feature.fromGeometry(point)})
@@ -78,15 +78,13 @@ public class CustomSpriteActivity extends AppCompatActivity {
fab.setImageResource(R.drawable.ic_directions_car_black);
} else {
// Update point
- point = Point.fromCoordinates(
- Position.fromCoordinates(point.getCoordinates().getLongitude() + 0.001,
- point.getCoordinates().getLatitude() + 0.001)
- );
+ point = Point.fromLngLat(point.longitude() + 0.001,
+ point.latitude() + 0.001);
source.setGeoJson(FeatureCollection.fromFeatures(new Feature[] {Feature.fromGeometry(point)}));
// Move the camera as well
mapboxMap.moveCamera(CameraUpdateFactory.newLatLng(new LatLng(
- point.getCoordinates().getLatitude(), point.getCoordinates().getLongitude())));
+ point.latitude(), point.longitude())));
}
}
});
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/FillExtrusionActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/FillExtrusionActivity.java
index a88a489cb1..15d7024abf 100644
--- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/FillExtrusionActivity.java
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/FillExtrusionActivity.java
@@ -12,7 +12,7 @@ import com.mapbox.mapboxsdk.maps.MapboxMap;
import com.mapbox.mapboxsdk.style.layers.FillExtrusionLayer;
import com.mapbox.mapboxsdk.style.sources.GeoJsonSource;
import com.mapbox.mapboxsdk.testapp.R;
-import com.mapbox.services.commons.geojson.Polygon;
+import com.mapbox.geojson.Polygon;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.fillExtrusionColor;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.fillExtrusionHeight;
@@ -35,7 +35,7 @@ public class FillExtrusionActivity extends AppCompatActivity {
mapView.onCreate(savedInstanceState);
mapView.getMapAsync(map -> {
mapboxMap = map;
- Polygon domTower = Polygon.fromCoordinates(new double[][][] {
+ Polygon domTower = Polygon.fromLngLats(new double[][][] {
new double[][] {
new double[] {
5.12112557888031,
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/GridSourceActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/GridSourceActivity.java
index 9dda0f8fa2..fdc3826fb1 100644
--- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/GridSourceActivity.java
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/GridSourceActivity.java
@@ -5,6 +5,10 @@ import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v7.app.AppCompatActivity;
+import com.mapbox.geojson.Feature;
+import com.mapbox.geojson.FeatureCollection;
+import com.mapbox.geojson.MultiLineString;
+import com.mapbox.geojson.Point;
import com.mapbox.mapboxsdk.geometry.LatLngBounds;
import com.mapbox.mapboxsdk.maps.MapView;
import com.mapbox.mapboxsdk.maps.MapboxMap;
@@ -13,10 +17,7 @@ import com.mapbox.mapboxsdk.style.layers.LineLayer;
import com.mapbox.mapboxsdk.style.sources.CustomGeometrySource;
import com.mapbox.mapboxsdk.style.sources.GeometryTileProvider;
import com.mapbox.mapboxsdk.testapp.R;
-import com.mapbox.services.commons.geojson.Feature;
-import com.mapbox.services.commons.geojson.FeatureCollection;
-import com.mapbox.services.commons.geojson.MultiLineString;
-import com.mapbox.services.commons.models.Position;
+
import java.util.ArrayList;
import java.util.Arrays;
@@ -68,18 +69,18 @@ public class GridSourceActivity extends AppCompatActivity implements OnMapReadyC
List gridLines = new ArrayList();
for (double y = Math.ceil(bounds.getLatNorth() / gridSpacing) * gridSpacing;
y >= Math.floor(bounds.getLatSouth() / gridSpacing) * gridSpacing; y -= gridSpacing) {
- gridLines.add(Arrays.asList(Position.fromCoordinates(bounds.getLonWest(), y),
- Position.fromCoordinates(bounds.getLonEast(), y)));
+ gridLines.add(Arrays.asList(Point.fromLngLat(bounds.getLonWest(), y),
+ Point.fromLngLat(bounds.getLonEast(), y)));
}
- features.add(Feature.fromGeometry(MultiLineString.fromCoordinates(gridLines)));
+ features.add(Feature.fromGeometry(MultiLineString.fromLngLats(gridLines)));
gridLines = new ArrayList();
for (double x = Math.floor(bounds.getLonWest() / gridSpacing) * gridSpacing;
x <= Math.ceil(bounds.getLonEast() / gridSpacing) * gridSpacing; x += gridSpacing) {
- gridLines.add(Arrays.asList(Position.fromCoordinates(x, bounds.getLatSouth()),
- Position.fromCoordinates(x, bounds.getLatNorth())));
+ gridLines.add(Arrays.asList(Point.fromLngLat(x, bounds.getLatSouth()),
+ Point.fromLngLat(x, bounds.getLatNorth())));
}
- features.add(Feature.fromGeometry(MultiLineString.fromCoordinates(gridLines)));
+ features.add(Feature.fromGeometry(MultiLineString.fromLngLats(gridLines)));
return FeatureCollection.fromFeatures(features);
}
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/HeatmapLayerActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/HeatmapLayerActivity.java
new file mode 100644
index 0000000000..b42734ea67
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/HeatmapLayerActivity.java
@@ -0,0 +1,216 @@
+package com.mapbox.mapboxsdk.testapp.activity.style;
+
+import android.os.Bundle;
+import android.support.v7.app.AppCompatActivity;
+
+import com.mapbox.mapboxsdk.maps.MapView;
+import com.mapbox.mapboxsdk.maps.MapboxMap;
+import com.mapbox.mapboxsdk.style.layers.CircleLayer;
+import com.mapbox.mapboxsdk.style.layers.HeatmapLayer;
+import com.mapbox.mapboxsdk.style.sources.GeoJsonSource;
+import com.mapbox.mapboxsdk.testapp.R;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import timber.log.Timber;
+
+import static com.mapbox.mapboxsdk.style.expressions.Expression.get;
+import static com.mapbox.mapboxsdk.style.expressions.Expression.interpolate;
+import static com.mapbox.mapboxsdk.style.expressions.Expression.linear;
+import static com.mapbox.mapboxsdk.style.expressions.Expression.literal;
+import static com.mapbox.mapboxsdk.style.expressions.Expression.rgb;
+import static com.mapbox.mapboxsdk.style.expressions.Expression.rgba;
+import static com.mapbox.mapboxsdk.style.expressions.Expression.stop;
+import static com.mapbox.mapboxsdk.style.expressions.Expression.zoom;
+import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.circleColor;
+import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.circleOpacity;
+import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.circleRadius;
+import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.circleStrokeColor;
+import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.circleStrokeWidth;
+import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.heatmapIntensity;
+import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.heatmapOpacity;
+import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.heatmapRadius;
+import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.heatmapWeight;
+
+/**
+ * Test activity showcasing the heatmap layer api.
+ */
+public class HeatmapLayerActivity extends AppCompatActivity {
+
+ private static final String EARTHQUAKE_SOURCE_URL = "https://www.mapbox.com/mapbox-gl-js/assets/earthquakes.geojson";
+ private static final String EARTHQUAKE_SOURCE_ID = "earthquakes";
+ private static final String HEATMAP_LAYER_ID = "earthquakes-heat";
+ private static final String HEATMAP_LAYER_SOURCE = "earthquakes";
+ private static final String CIRCLE_LAYER_ID = "earthquakes-circle";
+
+ private MapView mapView;
+ private MapboxMap mapboxMap;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_heatmaplayer);
+
+ mapView = (MapView) findViewById(R.id.mapView);
+ mapView.onCreate(savedInstanceState);
+ mapView.getMapAsync(map -> {
+ mapboxMap = map;
+ addEarthquakeSource();
+ addHeatmapLayer();
+ addCircleLayer();
+ });
+ }
+
+ private void addEarthquakeSource() {
+ try {
+ mapboxMap.addSource(new GeoJsonSource(EARTHQUAKE_SOURCE_ID, new URL(EARTHQUAKE_SOURCE_URL)));
+ } catch (MalformedURLException malformedUrlException) {
+ Timber.e(malformedUrlException, "That's not an url... ");
+ }
+ }
+
+ private void addHeatmapLayer() {
+ HeatmapLayer layer = new HeatmapLayer(HEATMAP_LAYER_ID, EARTHQUAKE_SOURCE_ID);
+ layer.setMaxZoom(9);
+ layer.setSourceLayer(HEATMAP_LAYER_SOURCE);
+ layer.setProperties(
+
+ // TODO add heatmap color https://github.com/mapbox/mapbox-gl-native/issues/11172
+ // Color ramp for heatmap. Domain is 0 (low) to 1 (high).
+ // Begin color ramp at 0-stop with a 0-transparancy color
+ // to create a blur-like effect.
+ //heatmapColor(),
+
+ // Increase the heatmap weight based on frequency and property magnitude
+ heatmapWeight(
+ interpolate(
+ linear(), get("mag"),
+ stop(0, 0),
+ stop(6, 1)
+ )
+ ),
+
+ // Increase the heatmap color weight weight by zoom level
+ // heatmap-intensity is a multiplier on top of heatmap-weight
+ heatmapIntensity(
+ interpolate(
+ linear(), zoom(),
+ stop(0, 1),
+ stop(9, 3)
+ )
+ ),
+
+ // Adjust the heatmap radius by zoom level
+ heatmapRadius(
+ interpolate(
+ linear(), zoom(),
+ stop(0, 2),
+ stop(9, 20)
+ )
+ ),
+
+ // Transition from heatmap to circle layer by zoom level
+ heatmapOpacity(
+ interpolate(
+ linear(), zoom(),
+ stop(7, 1),
+ stop(9, 0)
+ )
+ )
+ );
+
+ mapboxMap.addLayerAbove(layer, "waterway-label");
+ }
+
+ private void addCircleLayer() {
+ CircleLayer circleLayer = new CircleLayer(CIRCLE_LAYER_ID, EARTHQUAKE_SOURCE_ID);
+ circleLayer.setProperties(
+
+ // Size circle radius by earthquake magnitude and zoom level
+ circleRadius(
+ interpolate(
+ linear(), zoom(),
+ literal(7), interpolate(
+ linear(), get("mag"),
+ stop(1, 1),
+ stop(6, 4)
+ ),
+ literal(16), interpolate(
+ linear(), get("mag"),
+ stop(1, 5),
+ stop(6, 50)
+ )
+ )
+ ),
+
+ // Color circle by earthquake magnitude
+ circleColor(
+ interpolate(
+ linear(), get("mag"),
+ literal(1), rgba(33, 102, 172, 0),
+ literal(2), rgb(103, 169, 207),
+ literal(3), rgb(209, 229, 240),
+ literal(4), rgb(253, 219, 199),
+ literal(5), rgb(239, 138, 98),
+ literal(6), rgb(178, 24, 43)
+ )
+ ),
+
+ // Transition from heatmap to circle layer by zoom level
+ circleOpacity(
+ interpolate(
+ linear(), zoom(),
+ stop(7, 0),
+ stop(8, 1)
+ )
+ ),
+ circleStrokeColor("white"),
+ circleStrokeWidth(1.0f)
+ );
+
+ mapboxMap.addLayerBelow(circleLayer, HEATMAP_LAYER_ID);
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ mapView.onStart();
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ mapView.onResume();
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ mapView.onPause();
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ mapView.onStop();
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ mapView.onSaveInstanceState(outState);
+ }
+
+ @Override
+ public void onLowMemory() {
+ super.onLowMemory();
+ mapView.onLowMemory();
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ mapView.onDestroy();
+ }
+}
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/RuntimeStyleActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/RuntimeStyleActivity.java
index 942ce9aa3d..6eb4772b15 100644
--- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/RuntimeStyleActivity.java
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/RuntimeStyleActivity.java
@@ -8,6 +8,8 @@ import android.view.Menu;
import android.view.MenuItem;
import android.widget.Toast;
+import com.mapbox.geojson.Feature;
+import com.mapbox.geojson.FeatureCollection;
import com.mapbox.mapboxsdk.camera.CameraUpdateFactory;
import com.mapbox.mapboxsdk.geometry.LatLng;
import com.mapbox.mapboxsdk.maps.MapView;
@@ -28,8 +30,7 @@ import com.mapbox.mapboxsdk.style.sources.TileSet;
import com.mapbox.mapboxsdk.style.sources.VectorSource;
import com.mapbox.mapboxsdk.testapp.R;
import com.mapbox.mapboxsdk.testapp.utils.ResourceUtils;
-import com.mapbox.services.commons.geojson.Feature;
-import com.mapbox.services.commons.geojson.FeatureCollection;
+
import java.io.IOException;
import java.util.ArrayList;
@@ -364,7 +365,7 @@ public class RuntimeStyleActivity extends AppCompatActivity {
Timber.d("Updating parks source");
// change the source
- int park = counter < parks.getFeatures().size() - 1 ? counter : 0;
+ int park = counter < parks.features().size() - 1 ? counter : 0;
GeoJsonSource source = mapboxMap.getSourceAs("dynamic-park-source");
@@ -375,7 +376,7 @@ public class RuntimeStyleActivity extends AppCompatActivity {
}
List<Feature> features = new ArrayList<>();
- features.add(parks.getFeatures().get(park));
+ features.add(parks.features().get(park));
source.setGeoJson(FeatureCollection.fromFeatures(features));
// Re-post
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/SymbolGeneratorActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/SymbolGeneratorActivity.java
index 1ef59db9b1..ca4176be6e 100644
--- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/SymbolGeneratorActivity.java
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/SymbolGeneratorActivity.java
@@ -15,7 +15,8 @@ import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
-import com.google.gson.GsonBuilder;
+import com.mapbox.geojson.Feature;
+import com.mapbox.geojson.FeatureCollection;
import com.mapbox.mapboxsdk.maps.MapView;
import com.mapbox.mapboxsdk.maps.MapboxMap;
import com.mapbox.mapboxsdk.maps.OnMapReadyCallback;
@@ -25,12 +26,7 @@ import com.mapbox.mapboxsdk.style.sources.GeoJsonSource;
import com.mapbox.mapboxsdk.style.sources.Source;
import com.mapbox.mapboxsdk.testapp.R;
import com.mapbox.mapboxsdk.testapp.utils.ResourceUtils;
-import com.mapbox.services.commons.geojson.Feature;
-import com.mapbox.services.commons.geojson.FeatureCollection;
-import com.mapbox.services.commons.geojson.Geometry;
-import com.mapbox.services.commons.geojson.custom.GeometryDeserializer;
-import com.mapbox.services.commons.geojson.custom.PositionDeserializer;
-import com.mapbox.services.commons.models.Position;
+
import java.io.IOException;
import java.util.HashMap;
@@ -211,14 +207,8 @@ public class SymbolGeneratorActivity extends AppCompatActivity implements OnMapR
try {
// read local geojson from raw folder
String tinyCountriesJson = ResourceUtils.readRawResource(activity, R.raw.tiny_countries);
+ return FeatureCollection.fromJson(tinyCountriesJson);
- // convert geojson to a model
- FeatureCollection featureCollection = new GsonBuilder()
- .registerTypeAdapter(Geometry.class, new GeometryDeserializer())
- .registerTypeAdapter(Position.class, new PositionDeserializer())
- .create().fromJson(tinyCountriesJson, FeatureCollection.class);
-
- return featureCollection;
} catch (IOException exception) {
return null;
}
@@ -288,7 +278,7 @@ public class SymbolGeneratorActivity extends AppCompatActivity implements OnMapR
FeatureCollection featureCollection = params[0];
HashMap<String, Bitmap> imagesMap = new HashMap<>();
- for (Feature feature : featureCollection.getFeatures()) {
+ for (Feature feature : featureCollection.features()) {
String countryName = feature.getStringProperty(FEATURE_ID);
TextView textView = new TextView(context);
textView.setBackgroundColor(context.getResources().getColor(R.color.blueAccent));
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/SymbolLayerActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/SymbolLayerActivity.java
index d89d71e604..e3a4f4be93 100644
--- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/SymbolLayerActivity.java
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/SymbolLayerActivity.java
@@ -11,6 +11,9 @@ import android.view.MenuItem;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
+import com.mapbox.geojson.Feature;
+import com.mapbox.geojson.FeatureCollection;
+import com.mapbox.geojson.Point;
import com.mapbox.mapboxsdk.camera.CameraUpdateFactory;
import com.mapbox.mapboxsdk.geometry.LatLng;
import com.mapbox.mapboxsdk.maps.MapView;
@@ -18,9 +21,7 @@ import com.mapbox.mapboxsdk.maps.MapboxMap;
import com.mapbox.mapboxsdk.style.layers.SymbolLayer;
import com.mapbox.mapboxsdk.style.sources.GeoJsonSource;
import com.mapbox.mapboxsdk.testapp.R;
-import com.mapbox.services.commons.geojson.Feature;
-import com.mapbox.services.commons.geojson.FeatureCollection;
-import com.mapbox.services.commons.geojson.Point;
+
import java.util.Arrays;
import java.util.List;
@@ -62,8 +63,8 @@ public class SymbolLayerActivity extends AppCompatActivity implements MapboxMap.
// Add a source
FeatureCollection markers = FeatureCollection.fromFeatures(new Feature[] {
- Feature.fromGeometry(Point.fromCoordinates(new double[] {4.91638, 52.35673}), featureProperties("Marker 1")),
- Feature.fromGeometry(Point.fromCoordinates(new double[] {4.91638, 52.34673}), featureProperties("Marker 2"))
+ Feature.fromGeometry(Point.fromLngLat(4.91638, 52.35673), featureProperties("Marker 1")),
+ Feature.fromGeometry(Point.fromLngLat(4.91638, 52.34673), featureProperties("Marker 2"))
});
mapboxMap.addSource(new GeoJsonSource(MARKER_SOURCE, markers));
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/ZoomFunctionSymbolLayerActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/ZoomFunctionSymbolLayerActivity.java
index 4a6e62ef7d..180e2e726a 100644
--- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/ZoomFunctionSymbolLayerActivity.java
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/ZoomFunctionSymbolLayerActivity.java
@@ -7,16 +7,16 @@ import android.view.Menu;
import android.view.MenuItem;
import com.google.gson.JsonObject;
+import com.mapbox.geojson.Feature;
+import com.mapbox.geojson.FeatureCollection;
+import com.mapbox.geojson.Point;
import com.mapbox.mapboxsdk.maps.MapView;
import com.mapbox.mapboxsdk.maps.MapboxMap;
import com.mapbox.mapboxsdk.style.layers.Property;
import com.mapbox.mapboxsdk.style.layers.SymbolLayer;
import com.mapbox.mapboxsdk.style.sources.GeoJsonSource;
import com.mapbox.mapboxsdk.testapp.R;
-import com.mapbox.services.commons.geojson.Feature;
-import com.mapbox.services.commons.geojson.FeatureCollection;
-import com.mapbox.services.commons.geojson.Point;
-import com.mapbox.services.commons.models.Position;
+
import java.util.List;
@@ -87,15 +87,13 @@ public class ZoomFunctionSymbolLayerActivity extends AppCompatActivity {
}
private FeatureCollection createFeatureCollection() {
- Position position = isInitialPosition
- ? Position.fromCoordinates(-74.01618140, 40.701745)
- : Position.fromCoordinates(-73.988097, 40.749864);
+ Point point = isInitialPosition
+ ? Point.fromLngLat(-74.01618140, 40.701745)
+ : Point.fromLngLat(-73.988097, 40.749864);
- Point point = Point.fromCoordinates(position);
- Feature feature = Feature.fromGeometry(point);
JsonObject properties = new JsonObject();
properties.addProperty(KEY_PROPERTY_SELECTED, isSelected);
- feature.setProperties(properties);
+ Feature feature = Feature.fromGeometry(point, properties);
return FeatureCollection.fromFeatures(new Feature[] {feature});
}
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/userlocation/BaseLocationActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/userlocation/BaseLocationActivity.java
index 71b8115d2e..eec26cc9a7 100644
--- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/userlocation/BaseLocationActivity.java
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/userlocation/BaseLocationActivity.java
@@ -9,8 +9,8 @@ import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils;
-import com.mapbox.services.android.telemetry.permissions.PermissionsListener;
-import com.mapbox.services.android.telemetry.permissions.PermissionsManager;
+import com.mapbox.android.core.permissions.PermissionsListener;
+import com.mapbox.android.core.permissions.PermissionsManager;
import java.util.List;
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/userlocation/MockLocationEngine.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/userlocation/MockLocationEngine.java
index f4b54551bf..f4fe710de1 100644
--- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/userlocation/MockLocationEngine.java
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/userlocation/MockLocationEngine.java
@@ -5,8 +5,8 @@ import android.animation.TypeEvaluator;
import android.animation.ValueAnimator;
import android.location.Location;
-import com.mapbox.services.android.telemetry.location.LocationEngine;
-import com.mapbox.services.android.telemetry.location.LocationEngineListener;
+import com.mapbox.android.core.location.LocationEngine;
+import com.mapbox.android.core.location.LocationEngineListener;
import timber.log.Timber;
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/userlocation/MyLocationDrawableActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/userlocation/MyLocationDrawableActivity.java
index 000042306f..f603050030 100644
--- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/userlocation/MyLocationDrawableActivity.java
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/userlocation/MyLocationDrawableActivity.java
@@ -15,7 +15,7 @@ import com.mapbox.mapboxsdk.maps.MapView;
import com.mapbox.mapboxsdk.maps.MapboxMap;
import com.mapbox.mapboxsdk.maps.MapboxMapOptions;
import com.mapbox.mapboxsdk.testapp.R;
-import com.mapbox.services.android.telemetry.location.LocationEngineListener;
+import com.mapbox.android.core.location.LocationEngineListener;
/**
* Test activity showcasing how to change the MyLocationView drawable.
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/userlocation/MyLocationTintActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/userlocation/MyLocationTintActivity.java
index 89774dc507..ff3c4dfbc0 100644
--- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/userlocation/MyLocationTintActivity.java
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/userlocation/MyLocationTintActivity.java
@@ -18,7 +18,7 @@ import com.mapbox.mapboxsdk.maps.MapboxMap;
import com.mapbox.mapboxsdk.maps.TrackingSettings;
import com.mapbox.mapboxsdk.maps.widgets.MyLocationViewSettings;
import com.mapbox.mapboxsdk.testapp.R;
-import com.mapbox.services.android.telemetry.location.LocationEngineListener;
+import com.mapbox.android.core.location.LocationEngineListener;
/**
* Test activity showcasing how to tint the MyLocationView.
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/userlocation/MyLocationTrackingModeActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/userlocation/MyLocationTrackingModeActivity.java
index 5ebe43e68c..ffbb2c1a90 100644
--- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/userlocation/MyLocationTrackingModeActivity.java
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/userlocation/MyLocationTrackingModeActivity.java
@@ -25,7 +25,7 @@ import com.mapbox.mapboxsdk.maps.OnMapReadyCallback;
import com.mapbox.mapboxsdk.maps.TrackingSettings;
import com.mapbox.mapboxsdk.maps.UiSettings;
import com.mapbox.mapboxsdk.testapp.R;
-import com.mapbox.services.android.telemetry.location.LocationEngineListener;
+import com.mapbox.android.core.location.LocationEngineListener;
import timber.log.Timber;
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/utils/GeoParseUtil.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/utils/GeoParseUtil.java
index 97f70e33bb..c21e479659 100644
--- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/utils/GeoParseUtil.java
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/utils/GeoParseUtil.java
@@ -2,11 +2,11 @@ package com.mapbox.mapboxsdk.testapp.utils;
import android.content.Context;
import android.text.TextUtils;
+
+import com.mapbox.geojson.Feature;
+import com.mapbox.geojson.FeatureCollection;
+import com.mapbox.geojson.Point;
import com.mapbox.mapboxsdk.geometry.LatLng;
-import com.mapbox.services.commons.geojson.Feature;
-import com.mapbox.services.commons.geojson.FeatureCollection;
-import com.mapbox.services.commons.geojson.Point;
-import com.mapbox.services.commons.models.Position;
import java.io.BufferedReader;
import java.io.IOException;
@@ -31,10 +31,10 @@ public class GeoParseUtil {
public static List<LatLng> parseGeoJsonCoordinates(String geojsonStr) {
List<LatLng> latLngs = new ArrayList<>();
FeatureCollection featureCollection = FeatureCollection.fromJson(geojsonStr);
- for (Feature feature : featureCollection.getFeatures()) {
- if (feature.getGeometry() instanceof Point) {
- Position point = ((Point) feature.getGeometry()).getCoordinates();
- latLngs.add(new LatLng(point.getLatitude(), point.getLongitude()));
+ for (Feature feature : featureCollection.features()) {
+ if (feature.geometry() instanceof Point) {
+ Point point = (Point) feature.geometry();
+ latLngs.add(new LatLng(point.latitude(), point.longitude()));
}
}
return latLngs;
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_animated_marker.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_animated_marker.xml
index 0566757d58..252af714e7 100644
--- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_animated_marker.xml
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_animated_marker.xml
@@ -10,9 +10,9 @@
android:id="@id/mapView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- app:mapbox_cameraTargetLat="51.502615"
- app:mapbox_cameraTargetLng="4.972326"
- app:mapbox_cameraZoom="6"
+ app:mapbox_cameraTargetLat="38.90962"
+ app:mapbox_cameraTargetLng="-77.04341"
+ app:mapbox_cameraZoom="15"
app:mapbox_styleUrl="@string/mapbox_style_light"/>
</LinearLayout>
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_heatmaplayer.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_heatmaplayer.xml
new file mode 100644
index 0000000000..23ef9ea336
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_heatmaplayer.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <com.mapbox.mapboxsdk.maps.MapView
+ android:id="@id/mapView"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ app:mapbox_styleUrl="@string/mapbox_style_dark"/>
+
+</RelativeLayout>
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/descriptions.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/descriptions.xml
index e867046c80..9d44ada937 100644
--- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/descriptions.xml
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/descriptions.xml
@@ -24,7 +24,7 @@
<string name="description_offline">Offline Map example</string>
<string name="description_update_metadata">Update metadata example</string>
<string name="description_offline_region_delete">Delete region example</string>
- <string name="description_animated_marker">Animate the position change of a marker</string>
+ <string name="description_animated_symbollayer">Animate the position change of a symbol layer</string>
<string name="description_polyline">Add a polyline to a map</string>
<string name="description_polygon">Add a polygon to a map</string>
<string name="description_scroll_by">Scroll with pixels in x,y direction</string>
@@ -70,4 +70,5 @@
<string name="description_grid_source">Example Custom Geometry Source</string>
<string name="description_local_glyph">Suzhou using Droid Sans for Chinese glyphs</string>
<string name="description_hillshade">Example raster-dem source and hillshade layer</string>
+ <string name="description_heatmaplayer">Use HeatmapLayer to visualise earthquakes</string>
</resources>
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/titles.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/titles.xml
index 47fee31c0a..352d7884a6 100644
--- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/titles.xml
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/titles.xml
@@ -4,7 +4,7 @@
<string name="activity_map_fragment">Map Fragment</string>
<string name="activity_multimap">Multiple Maps on Screen</string>
<string name="activity_add_bulk_markers">Add Markers In Bulk</string>
- <string name="activity_animated_marker">Animated Markers</string>
+ <string name="activity_animated_symbollayer">Animated SymbolLayer</string>
<string name="activity_dynamic_marker">Dynamic Marker</string>
<string name="activity_polyline">Polyline</string>
<string name="activity_polygon">Polygon</string>
@@ -70,4 +70,5 @@
<string name="activity_grid_source">Grid Source</string>
<string name="activity_local_glyph">Local CJK glyph generation</string>
<string name="activity_hillshade">Hillshade</string>
+ <string name="activity_heatmaplayer">Heatmap layer</string>
</resources> \ No newline at end of file
diff --git a/platform/android/build.gradle b/platform/android/build.gradle
index 0368558e92..6cd9505447 100644
--- a/platform/android/build.gradle
+++ b/platform/android/build.gradle
@@ -11,6 +11,7 @@ buildscript {
allprojects {
repositories {
+ mavenCentral()
jcenter()
google()
maven { url "http://oss.sonatype.org/content/repositories/snapshots/" }
diff --git a/platform/android/config.cmake b/platform/android/config.cmake
index 4832f8b251..30182bbc06 100644
--- a/platform/android/config.cmake
+++ b/platform/android/config.cmake
@@ -158,6 +158,8 @@ add_library(mbgl-android STATIC
platform/android/src/style/layers/fill_extrusion_layer.hpp
platform/android/src/style/layers/fill_layer.cpp
platform/android/src/style/layers/fill_layer.hpp
+ platform/android/src/style/layers/heatmap_layer.cpp
+ platform/android/src/style/layers/heatmap_layer.hpp
platform/android/src/style/layers/hillshade_layer.cpp
platform/android/src/style/layers/hillshade_layer.hpp
platform/android/src/style/layers/layer.cpp
@@ -248,8 +250,6 @@ add_library(mbgl-android STATIC
platform/android/src/geojson/point.hpp
platform/android/src/geojson/polygon.cpp
platform/android/src/geojson/polygon.hpp
- platform/android/src/geojson/position.cpp
- platform/android/src/geojson/position.hpp
# Geometry
platform/android/src/geometry/lat_lng.cpp
diff --git a/platform/android/gradle/dependencies.gradle b/platform/android/gradle/dependencies.gradle
index 0c72368d3d..695cca3a29 100644
--- a/platform/android/gradle/dependencies.gradle
+++ b/platform/android/gradle/dependencies.gradle
@@ -8,23 +8,29 @@ ext {
]
versions = [
- mapboxServices: '2.2.9',
- supportLib : '25.4.0',
- espresso : '3.0.1',
- testRunner : '1.0.1',
- leakCanary : '1.5.1',
- lost : '3.0.4',
- junit : '4.12',
- mockito : '2.10.0',
- robolectric : '3.5.1',
- timber : '4.5.1',
- okhttp : '3.9.1'
+ mapboxServices : '3.0.0-beta.2',
+ mapboxTelemetry: '3.0.0-beta.1',
+ mapboxGestures : '0.1.0-20180227.133736-12',
+ supportLib : '25.4.0',
+ espresso : '3.0.1',
+ testRunner : '1.0.1',
+ leakCanary : '1.5.1',
+ lost : '3.0.4',
+ junit : '4.12',
+ mockito : '2.10.0',
+ robolectric : '3.5.1',
+ timber : '4.5.1',
+ okhttp : '3.9.1'
]
dependenciesList = [
- mapboxJavaServices : "com.mapbox.mapboxsdk:mapbox-java-services:${versions.mapboxServices}@jar",
- mapboxJavaGeoJSON : "com.mapbox.mapboxsdk:mapbox-java-geojson:${versions.mapboxServices}@jar",
- mapboxAndroidTelemetry: "com.mapbox.mapboxsdk:mapbox-android-telemetry:${versions.mapboxServices}@aar",
+ mapboxJavaServices : "com.mapbox.mapboxsdk:mapbox-sdk-services:${versions.mapboxServices}",
+ mapboxJavaGeoJSON : "com.mapbox.mapboxsdk:mapbox-sdk-geojson:${versions.mapboxServices}",
+ mapboxAndroidTelemetry: "com.mapbox.mapboxsdk:mapbox-android-telemetry:${versions.mapboxTelemetry}",
+ mapboxAndroidGestures : "com.mapbox.mapboxsdk:mapbox-android-gestures:${versions.mapboxGestures}@aar",
+
+ // for testApp
+ mapboxJavaTurf : "com.mapbox.mapboxsdk:mapbox-sdk-turf:${versions.mapboxServices}",
junit : "junit:junit:${versions.junit}",
mockito : "org.mockito:mockito-core:${versions.mockito}",
@@ -34,6 +40,7 @@ ext {
testRules : "com.android.support.test:rules:${versions.testRunner}",
testEspressoCore : "com.android.support.test.espresso:espresso-core:${versions.espresso}",
testEspressoIntents : "com.android.support.test.espresso:espresso-intents:${versions.espresso}",
+ testEspressoContrib : "com.android.support.test.espresso:espresso-contrib:${versions.espresso}",
supportAnnotations : "com.android.support:support-annotations:${versions.supportLib}",
supportAppcompatV7 : "com.android.support:appcompat-v7:${versions.supportLib}",
@@ -42,6 +49,7 @@ ext {
supportRecyclerView : "com.android.support:recyclerview-v7:${versions.supportLib}",
lost : "com.mapzen.android:lost:${versions.lost}",
+ gmsLocation : 'com.google.android.gms:play-services-location:11.0.4',
timber : "com.jakewharton.timber:timber:${versions.timber}",
okhttp3 : "com.squareup.okhttp3:okhttp:${versions.okhttp}",
leakCanaryDebug : "com.squareup.leakcanary:leakcanary-android:${versions.leakCanary}",
diff --git a/platform/android/scripts/exclude-activity-gen.json b/platform/android/scripts/exclude-activity-gen.json
index f05001c6ae..b2278c9d63 100644
--- a/platform/android/scripts/exclude-activity-gen.json
+++ b/platform/android/scripts/exclude-activity-gen.json
@@ -18,7 +18,7 @@
"LocationPickerActivity",
"GeoJsonClusteringActivity",
"RuntimeStyleTestActivity",
- "AnimatedMarkerActivity",
+ "AnimatedSymbolLayerActivity",
"ViewPagerActivity",
"MapFragmentActivity",
"SupportMapFragmentActivity",
@@ -27,5 +27,10 @@
"QueryRenderedFeaturesBoxHighlightActivity",
"MultiMapActivity",
"MapInDialogActivity",
- "SimpleMapActivity"
+ "SimpleMapActivity",
+ "ManualZoomActivity",
+ "MaxMinZoomActivity",
+ "ScrollByActivity",
+ "ZoomFunctionSymbolLayerActivity",
+ "SymbolGeneratorActivity"
] \ No newline at end of file
diff --git a/platform/android/scripts/generate-style-code.js b/platform/android/scripts/generate-style-code.js
index 65b0a399ac..6e6d3cfa67 100755
--- a/platform/android/scripts/generate-style-code.js
+++ b/platform/android/scripts/generate-style-code.js
@@ -28,6 +28,9 @@ var layers = Object.keys(spec.layer.type.values).map((type) => {
}, []);
const paintProperties = Object.keys(spec[`paint_${type}`]).reduce((memo, name) => {
+ // disabled for now, see https://github.com/mapbox/mapbox-gl-native/issues/11172
+ if (name === 'heatmap-color') return memo;
+
spec[`paint_${type}`][name].name = name;
memo.push(spec[`paint_${type}`][name]);
return memo;
diff --git a/platform/android/src/android_renderer_frontend.cpp b/platform/android/src/android_renderer_frontend.cpp
index afdb08a10e..2a03d9de9e 100644
--- a/platform/android/src/android_renderer_frontend.cpp
+++ b/platform/android/src/android_renderer_frontend.cpp
@@ -77,8 +77,8 @@ void AndroidRendererFrontend::update(std::shared_ptr<UpdateParameters> params) {
mapRenderer.requestRender();
}
-void AndroidRendererFrontend::onLowMemory() {
- mapRenderer.actor().invoke(&Renderer::onLowMemory);
+void AndroidRendererFrontend::reduceMemoryUse() {
+ mapRenderer.actor().invoke(&Renderer::reduceMemoryUse);
}
std::vector<Feature> AndroidRendererFrontend::querySourceFeatures(const std::string& sourceID,
diff --git a/platform/android/src/android_renderer_frontend.hpp b/platform/android/src/android_renderer_frontend.hpp
index 178870c452..b61904e388 100644
--- a/platform/android/src/android_renderer_frontend.hpp
+++ b/platform/android/src/android_renderer_frontend.hpp
@@ -39,7 +39,7 @@ public:
AnnotationIDs queryShapeAnnotations(const ScreenBox& box) const;
// Memory
- void onLowMemory();
+ void reduceMemoryUse();
private:
MapRenderer& mapRenderer;
diff --git a/platform/android/src/conversion/collection.hpp b/platform/android/src/conversion/collection.hpp
index 549121c7ef..2b953e73f4 100644
--- a/platform/android/src/conversion/collection.hpp
+++ b/platform/android/src/conversion/collection.hpp
@@ -28,6 +28,7 @@ inline jni::jobject* toArrayList(JNIEnv& env, jni::jarray<T>& array) {
inline std::vector<std::string> toVector(JNIEnv& env, jni::jarray<jni::jobject>& array) {
std::vector<std::string> vector;
std::size_t len = jni::GetArrayLength(env, array);
+ vector.reserve(len);
for (std::size_t i = 0; i < len; i++) {
jni::jstring* jstr = reinterpret_cast<jni::jstring*>(jni::GetObjectArrayElement(env, array, i));
diff --git a/platform/android/src/example_custom_layer.cpp b/platform/android/src/example_custom_layer.cpp
index 1ed68d0835..f7b425c40a 100644
--- a/platform/android/src/example_custom_layer.cpp
+++ b/platform/android/src/example_custom_layer.cpp
@@ -1,17 +1,121 @@
#include <jni.h>
#include <GLES2/gl2.h>
+#include <sstream>
+#include <android/log.h>
+#include <mbgl/style/layers/custom_layer.hpp>
-#include <mbgl/util/logging.hpp>
+// DEBUGGING
-#include <mbgl/style/layers/custom_layer.hpp>
+const char* LOG_TAG = "Custom Layer Example";
+
+const char* stringFromError(GLenum err) {
+ switch (err) {
+ case GL_INVALID_ENUM:
+ return "GL_INVALID_ENUM";
+
+ case GL_INVALID_VALUE:
+ return "GL_INVALID_VALUE";
+
+ case GL_INVALID_OPERATION:
+ return "GL_INVALID_OPERATION";
+
+ case GL_INVALID_FRAMEBUFFER_OPERATION:
+ return "GL_INVALID_FRAMEBUFFER_OPERATION";
+
+ case GL_OUT_OF_MEMORY:
+ return "GL_OUT_OF_MEMORY";
+
+#ifdef GL_TABLE_TOO_LARGE
+ case GL_TABLE_TOO_LARGE:
+ return "GL_TABLE_TOO_LARGE";
+#endif
+
+#ifdef GL_STACK_OVERFLOW
+ case GL_STACK_OVERFLOW:
+ return "GL_STACK_OVERFLOW";
+#endif
+
+#ifdef GL_STACK_UNDERFLOW
+ case GL_STACK_UNDERFLOW:
+ return "GL_STACK_UNDERFLOW";
+#endif
+
+#ifdef GL_CONTEXT_LOST
+ case GL_CONTEXT_LOST:
+ return "GL_CONTEXT_LOST";
+#endif
+
+ default:
+ return "GL_UNKNOWN";
+ }
+}
+
+struct Error : std::runtime_error {
+ using std::runtime_error::runtime_error;
+};
+
+void checkError(const char *cmd, const char *file, int line) {
+
+ GLenum err = GL_NO_ERROR;
+ if ((err = glGetError()) != GL_NO_ERROR) {
+ std::ostringstream message;
+ message << cmd << ": Error " << stringFromError(err);
+
+ // Check for further errors
+ while ((err = glGetError()) != GL_NO_ERROR) {
+ message << ", " << stringFromError(err);
+ }
+
+ message << " at " << file << ":" << line;
+ __android_log_write(ANDROID_LOG_ERROR, LOG_TAG, message.str().c_str());
+ throw Error(message.str());
+ }
+}
+
+#ifndef NDEBUG
+#define GL_CHECK_ERROR(cmd) ([&]() { struct __MBGL_C_E { ~__MBGL_C_E() noexcept(false) { checkError(#cmd, __FILE__, __LINE__); } } __MBGL_C_E; return cmd; }())
+#else
+#define GL_CHECK_ERROR(cmd) (cmd)
+#endif
+
+void checkLinkStatus(GLuint program) {
+ GLint isLinked = 0;
+ glGetProgramiv(program, GL_LINK_STATUS, &isLinked);
+ if (isLinked == GL_FALSE) {
+ GLint maxLength = 0;
+ glGetProgramiv(program, GL_INFO_LOG_LENGTH, &maxLength);
+ GLchar infoLog[maxLength];
+ glGetProgramInfoLog(program, maxLength, &maxLength, &infoLog[0]);
+ __android_log_write(ANDROID_LOG_ERROR, LOG_TAG, &infoLog[0]);
+ throw Error(infoLog);
+ }
+
+}
+
+void checkCompileStatus(GLuint shader) {
+ GLint isCompiled = 0;
+ glGetShaderiv(shader, GL_COMPILE_STATUS, &isCompiled);
+ if (isCompiled == GL_FALSE) {
+ GLint maxLength = 0;
+ glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &maxLength);
+
+ // The maxLength includes the NULL character
+ GLchar errorLog[maxLength];
+ glGetShaderInfoLog(shader, maxLength, &maxLength, &errorLog[0]);
+ __android_log_write(ANDROID_LOG_ERROR, LOG_TAG, &errorLog[0]);
+ throw Error(errorLog);
+ }
+}
+
+// /DEBUGGING
static const GLchar * vertexShaderSource = "attribute vec2 a_pos; void main() { gl_Position = vec4(a_pos, 0, 1); }";
-static const GLchar * fragmentShaderSource = "uniform vec4 fill_color; void main() { gl_FragColor = fill_color; }";
+static const GLchar * fragmentShaderSource = "uniform highp vec4 fill_color; void main() { gl_FragColor = fill_color; }";
class ExampleCustomLayer {
public:
~ExampleCustomLayer() {
- mbgl::Log::Info(mbgl::Event::General, "~ExampleCustomLayer");
+ __android_log_write(ANDROID_LOG_INFO, LOG_TAG, "~ExampleCustomLayer");
if (program) {
glDeleteBuffers(1, &buffer);
glDetachShader(program, vertexShader);
@@ -23,38 +127,49 @@ public:
}
void initialize() {
- mbgl::Log::Info(mbgl::Event::General, "Initialize");
- program = glCreateProgram();
- vertexShader = glCreateShader(GL_VERTEX_SHADER);
- fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
-
- glShaderSource(vertexShader, 1, &vertexShaderSource, nullptr);
- glCompileShader(vertexShader);
- glAttachShader(program, vertexShader);
- glShaderSource(fragmentShader, 1, &fragmentShaderSource, nullptr);
- glCompileShader(fragmentShader);
- glAttachShader(program, fragmentShader);
- glLinkProgram(program);
- a_pos = glGetAttribLocation(program, "a_pos");
- fill_color = glGetUniformLocation(program, "fill_color");
-
- GLfloat background[] = { -1,-1, 1,-1, -1,1, 1,1 };
- glGenBuffers(1, &buffer);
- glBindBuffer(GL_ARRAY_BUFFER, buffer);
- glBufferData(GL_ARRAY_BUFFER, 8 * sizeof(GLfloat), background, GL_STATIC_DRAW);
+ __android_log_write(ANDROID_LOG_INFO, LOG_TAG, "Initialize");
+
+ // Debug info
+ int maxAttrib;
+ GL_CHECK_ERROR(glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &maxAttrib));
+ __android_log_print(ANDROID_LOG_INFO, LOG_TAG, "Max vertex attributes: %i", maxAttrib);
+
+ program = GL_CHECK_ERROR(glCreateProgram());
+ vertexShader = GL_CHECK_ERROR(glCreateShader(GL_VERTEX_SHADER));
+ fragmentShader = GL_CHECK_ERROR(glCreateShader(GL_FRAGMENT_SHADER));
+
+ GL_CHECK_ERROR(glShaderSource(vertexShader, 1, &vertexShaderSource, nullptr));
+ GL_CHECK_ERROR(glCompileShader(vertexShader));
+ checkCompileStatus(vertexShader);
+ GL_CHECK_ERROR(glAttachShader(program, vertexShader));
+ GL_CHECK_ERROR(glShaderSource(fragmentShader, 1, &fragmentShaderSource, nullptr));
+ GL_CHECK_ERROR(glCompileShader(fragmentShader));
+ checkCompileStatus(fragmentShader);
+ GL_CHECK_ERROR(glAttachShader(program, fragmentShader));
+ GL_CHECK_ERROR(glLinkProgram(program));
+ checkLinkStatus(program);
+
+ a_pos = GL_CHECK_ERROR(glGetAttribLocation(program, "a_pos"));
+ fill_color = GL_CHECK_ERROR(glGetUniformLocation(program, "fill_color"));
+
+ GLfloat background[] = { -1, -1, 1, -1, -1, 1, 1, 1 };
+ GL_CHECK_ERROR(glGenBuffers(1, &buffer));
+ GL_CHECK_ERROR(glBindBuffer(GL_ARRAY_BUFFER, buffer));
+ GL_CHECK_ERROR(glBufferData(GL_ARRAY_BUFFER, 8 * sizeof(GLfloat), background, GL_STATIC_DRAW));
}
void render() {
- mbgl::Log::Info(mbgl::Event::General, "Render");
- glUseProgram(program);
- glBindBuffer(GL_ARRAY_BUFFER, buffer);
- glEnableVertexAttribArray(a_pos);
- glVertexAttribPointer(a_pos, 2, GL_FLOAT, GL_FALSE, 0, NULL);
- glDisable(GL_STENCIL_TEST);
- glDisable(GL_DEPTH_TEST);
- glUniform4fv(fill_color, 1, color);
-
- glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
+ __android_log_write(ANDROID_LOG_INFO, LOG_TAG, "Render");
+
+ GL_CHECK_ERROR(glUseProgram(program));
+ GL_CHECK_ERROR(glBindBuffer(GL_ARRAY_BUFFER, buffer));
+ GL_CHECK_ERROR(glEnableVertexAttribArray(a_pos));
+ GL_CHECK_ERROR(glVertexAttribPointer(a_pos, 2, GL_FLOAT, GL_FALSE, 0, NULL));
+ GL_CHECK_ERROR(glDisable(GL_STENCIL_TEST));
+ GL_CHECK_ERROR(glDisable(GL_DEPTH_TEST));
+ GL_CHECK_ERROR(glUniform4fv(fill_color, 1, color));
+ GL_CHECK_ERROR(glDrawArrays(GL_TRIANGLE_STRIP, 0, 4));
+
}
GLuint program = 0;
@@ -70,12 +185,12 @@ public:
GLfloat ExampleCustomLayer::color[] = { 0.0f, 1.0f, 0.0f, 1.0f };
jlong JNICALL nativeCreateContext(JNIEnv*, jobject) {
- mbgl::Log::Info(mbgl::Event::General, "nativeCreateContext");
+ __android_log_write(ANDROID_LOG_INFO, LOG_TAG, "nativeCreateContext");
return reinterpret_cast<jlong>(new ExampleCustomLayer());
}
void JNICALL nativeSetColor(JNIEnv*, jobject, jfloat red, jfloat green, jfloat blue, jfloat alpha) {
- mbgl::Log::Info(mbgl::Event::General, "nativeSetColor: %.2f, %.2f, %.2f, %.2f", red, green, blue, alpha);
+ __android_log_print(ANDROID_LOG_INFO, LOG_TAG, "nativeSetColor: %.2f, %.2f, %.2f, %.2f", red, green, blue, alpha);
ExampleCustomLayer::color[0] = red;
ExampleCustomLayer::color[1] = green;
ExampleCustomLayer::color[2] = blue;
@@ -83,26 +198,27 @@ void JNICALL nativeSetColor(JNIEnv*, jobject, jfloat red, jfloat green, jfloat b
}
void nativeInitialize(void *context) {
- mbgl::Log::Info(mbgl::Event::General, "nativeInitialize");
+ __android_log_write(ANDROID_LOG_INFO, LOG_TAG, "nativeInitialize");
reinterpret_cast<ExampleCustomLayer*>(context)->initialize();
}
void nativeRender(void *context, const mbgl::style::CustomLayerRenderParameters& /*parameters*/) {
- mbgl::Log::Info(mbgl::Event::General, "nativeRender");
+ __android_log_write(ANDROID_LOG_INFO, LOG_TAG, "nativeRender");
reinterpret_cast<ExampleCustomLayer*>(context)->render();
}
void nativeContextLost(void */*context*/) {
- mbgl::Log::Info(mbgl::Event::General, "nativeContextLost");
+ __android_log_write(ANDROID_LOG_INFO, LOG_TAG, "nativeContextLost");
}
-void nativeDenitialize(void *context) {
- mbgl::Log::Info(mbgl::Event::General, "nativeDeinitialize");
+void nativeDeinitialize(void *context) {
+ __android_log_write(ANDROID_LOG_INFO, LOG_TAG, "nativeDeinitialize");
delete reinterpret_cast<ExampleCustomLayer*>(context);
}
extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *) {
- mbgl::Log::Info(mbgl::Event::General, "OnLoad");
+ __android_log_write(ANDROID_LOG_INFO, LOG_TAG, "OnLoad");
+
JNIEnv *env = nullptr;
vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6);
@@ -132,7 +248,7 @@ extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *) {
env->SetStaticLongField(customLayerClass,
env->GetStaticFieldID(customLayerClass, "DeinitializeFunction", "J"),
- reinterpret_cast<jlong>(nativeDenitialize));
+ reinterpret_cast<jlong>(nativeDeinitialize));
return JNI_VERSION_1_6;
}
diff --git a/platform/android/src/file_source.cpp b/platform/android/src/file_source.cpp
index 6a9d7badb0..42c03b0974 100644
--- a/platform/android/src/file_source.cpp
+++ b/platform/android/src/file_source.cpp
@@ -81,6 +81,13 @@ void FileSource::pause(jni::JNIEnv&) {
}
}
+jni::jboolean FileSource::isResumed(jni::JNIEnv&) {
+ if (activationCounter) {
+ return (jboolean) (activationCounter > 0);
+ }
+ return (jboolean) false;
+}
+
jni::Class<FileSource> FileSource::javaClass;
FileSource* FileSource::getNativePeer(jni::JNIEnv& env, jni::Object<FileSource> jFileSource) {
@@ -112,7 +119,8 @@ void FileSource::registerNative(jni::JNIEnv& env) {
METHOD(&FileSource::setAPIBaseUrl, "setApiBaseUrl"),
METHOD(&FileSource::setResourceTransform, "setResourceTransform"),
METHOD(&FileSource::resume, "activate"),
- METHOD(&FileSource::pause, "deactivate")
+ METHOD(&FileSource::pause, "deactivate"),
+ METHOD(&FileSource::isResumed, "isActivated")
);
}
@@ -124,8 +132,11 @@ jni::Class<FileSource::ResourceTransformCallback> FileSource::ResourceTransformC
std::string FileSource::ResourceTransformCallback::onURL(jni::JNIEnv& env, jni::Object<FileSource::ResourceTransformCallback> callback, int kind, std::string url_) {
static auto method = FileSource::ResourceTransformCallback::javaClass.GetMethod<jni::String (jni::jint, jni::String)>(env, "onURL");
auto url = jni::Make<jni::String>(env, url_);
+
url = callback.Call(env, method, kind, url);
- return jni::Make<std::string>(env, url);
+ auto urlStr = jni::Make<std::string>(env, url);
+ jni::DeleteLocalRef(env, url);
+ return urlStr;
}
} // namespace android
diff --git a/platform/android/src/file_source.hpp b/platform/android/src/file_source.hpp
index 194f784622..e4295e1b84 100644
--- a/platform/android/src/file_source.hpp
+++ b/platform/android/src/file_source.hpp
@@ -45,6 +45,8 @@ public:
void pause(jni::JNIEnv&);
+ jni::jboolean isResumed(jni::JNIEnv&);
+
static jni::Class<FileSource> javaClass;
static FileSource* getNativePeer(jni::JNIEnv&, jni::Object<FileSource>);
diff --git a/platform/android/src/geojson/conversion/geometry.hpp b/platform/android/src/geojson/conversion/geometry.hpp
index 2ca63e2c11..5d2aab4c2d 100644
--- a/platform/android/src/geojson/conversion/geometry.hpp
+++ b/platform/android/src/geojson/conversion/geometry.hpp
@@ -21,88 +21,91 @@ public:
jni::JNIEnv& env;
/**
- * Point (double[])
+ * static Point fromLngLat(double longitude,double latitude)
*/
jni::jobject* operator()(const mapbox::geometry::point<T> &geometry) const {
- static jni::jclass* javaClass = jni::NewGlobalRef(env, &jni::FindClass(env, "com/mapbox/services/commons/geojson/Point")).release();
- static jni::jmethodID* fromCoordinates = &jni::GetStaticMethodID(env, *javaClass, "fromCoordinates", "([D)Lcom/mapbox/services/commons/geojson/Point;");
+ static jni::jclass* javaClass = jni::NewGlobalRef(env, &jni::FindClass(env, "com/mapbox/geojson/Point")).release();
+ static jni::jmethodID* fromLngLat = &jni::GetStaticMethodID(env, *javaClass, "fromLngLat", "(DD)Lcom/mapbox/geojson/Point;");
- // Create Point
- jni::LocalObject<jni::jarray<jni::jdouble>> position = jni::NewLocalObject(env, toGeoJsonPosition(env, geometry.x, geometry.y));
- return reinterpret_cast<jni::jobject*>(jni::CallStaticMethod<jni::jobject*>(env, *javaClass, *fromCoordinates, position.get()));
+ return reinterpret_cast<jni::jobject*>(jni::CallStaticMethod<jni::jobject*>(env, *javaClass, *fromLngLat, geometry.x, geometry.y));
}
/**
- * LineString (double[][])
+ * static LineString fromLngLats(List<Point> points)
*/
jni::jobject* operator()(const mapbox::geometry::line_string<T> &geometry) const {
- static jni::jclass* javaClass = jni::NewGlobalRef(env, &jni::FindClass(env, "com/mapbox/services/commons/geojson/LineString")).release();
- static jni::jmethodID* fromCoordinates = &jni::GetStaticMethodID(env, *javaClass, "fromCoordinates", "([[D)Lcom/mapbox/services/commons/geojson/LineString;");
+ static jni::jclass* javaClass = jni::NewGlobalRef(env, &jni::FindClass(env, "com/mapbox/geojson/LineString")).release();
+ static jni::jmethodID* fromLngLats = &jni::GetStaticMethodID(env, *javaClass, "fromLngLats", "(Ljava/util/List;)Lcom/mapbox/geojson/LineString;");
// Create
- jni::LocalObject<jni::jarray<jni::jobject>> coordinates = jni::NewLocalObject(env, toGeoJsonCoordinates(env, geometry));
- return reinterpret_cast<jni::jobject*>(jni::CallStaticMethod<jni::jobject*>(env, *javaClass, *fromCoordinates, coordinates.get()));
+ jni::LocalObject<jni::jobject> listOfPoints = jni::NewLocalObject(env, toGeoJsonListOfPoints(env, geometry));
+ return reinterpret_cast<jni::jobject*>(jni::CallStaticMethod<jni::jobject*>(env, *javaClass, *fromLngLats, listOfPoints.get()));
}
/**
- * MultiPoint (double[][])
+ * static MultiPoint fromLngLats(List<Point> points)
*/
jni::jobject* operator()(const mapbox::geometry::multi_point<T> &geometry) const {
- static jni::jclass* javaClass = jni::NewGlobalRef(env, &jni::FindClass(env, "com/mapbox/services/commons/geojson/MultiPoint")).release();
- static jni::jmethodID* fromCoordinates = &jni::GetStaticMethodID(env, *javaClass, "fromCoordinates", "([[D)Lcom/mapbox/services/commons/geojson/MultiPoint;");
+ static jni::jclass* javaClass = jni::NewGlobalRef(env, &jni::FindClass(env, "com/mapbox/geojson/MultiPoint")).release();
+ static jni::jmethodID* fromLngLats = &jni::GetStaticMethodID(env, *javaClass, "fromLngLats", "(Ljava/util/List;)Lcom/mapbox/geojson/MultiPoint;");
// Create
- jni::LocalObject<jni::jarray<jni::jobject>> coordinates = jni::NewLocalObject(env, toGeoJsonCoordinates(env, geometry));
- return reinterpret_cast<jni::jobject*>(jni::CallStaticMethod<jni::jobject*>(env, *javaClass, *fromCoordinates, coordinates.get()));
+ jni::LocalObject<jni::jobject> coordinates = jni::NewLocalObject(env, toGeoJsonListOfPoints(env, geometry));
+ return reinterpret_cast<jni::jobject*>(jni::CallStaticMethod<jni::jobject*>(env, *javaClass, *fromLngLats, coordinates.get()));
}
/**
- * Polygon (double[][][])
+ * static Polygon fromLngLats(List<List<Point>> coordinates)
*/
jni::jobject* operator()(const mapbox::geometry::polygon<T> &geometry) const {
- static jni::jclass* javaClass = jni::NewGlobalRef(env, &jni::FindClass(env, "com/mapbox/services/commons/geojson/Polygon")).release();
- static jni::jmethodID* fromCoordinates = &jni::GetStaticMethodID(env, *javaClass, "fromCoordinates", "([[[D)Lcom/mapbox/services/commons/geojson/Polygon;");
+ static jni::jclass* javaClass = jni::NewGlobalRef(env, &jni::FindClass(env, "com/mapbox/geojson/Polygon")).release();
+ static jni::jmethodID* fromLngLats = &jni::GetStaticMethodID(env, *javaClass, "fromLngLats", "(Ljava/util/List;)Lcom/mapbox/geojson/Polygon;");
// Create
- jni::LocalObject<jni::jarray<jni::jobject>> shape = jni::NewLocalObject(env, toShape<>(env, geometry));
- return reinterpret_cast<jni::jobject*>(jni::CallStaticMethod<jni::jobject*>(env, *javaClass, *fromCoordinates, shape.get()));
+ jni::LocalObject<jni::jobject> shape = jni::NewLocalObject(env, toShape<>(env, geometry));
+ return reinterpret_cast<jni::jobject*>(jni::CallStaticMethod<jni::jobject*>(env, *javaClass, *fromLngLats, shape.get()));
}
/**
- * MultiLineString (double[][][])
+ * static MultiLineString fromLngLats(List<List<Point>> points)
*/
jni::jobject* operator()(const mapbox::geometry::multi_line_string<T> &geometry) const {
- static jni::jclass* javaClass = jni::NewGlobalRef(env, &jni::FindClass(env, "com/mapbox/services/commons/geojson/MultiLineString")).release();
- static jni::jmethodID* fromCoordinates = &jni::GetStaticMethodID(env, *javaClass, "fromCoordinates", "([[[D)Lcom/mapbox/services/commons/geojson/MultiLineString;");
+ static jni::jclass* javaClass = jni::NewGlobalRef(env, &jni::FindClass(env, "com/mapbox/geojson/MultiLineString")).release();
+ static jni::jmethodID* fromLngLats = &jni::GetStaticMethodID(env, *javaClass, "fromLngLats", "(Ljava/util/List;)Lcom/mapbox/geojson/MultiLineString;");
// Create
- jni::LocalObject<jni::jarray<jni::jobject>> shape = jni::NewLocalObject(env, toShape<>(env, geometry));
- return reinterpret_cast<jni::jobject*>(jni::CallStaticMethod<jni::jobject*>(env, *javaClass, *fromCoordinates, shape.get()));
+ jni::LocalObject<jni::jobject> shape = jni::NewLocalObject(env, toShape<>(env, geometry));
+ return reinterpret_cast<jni::jobject*>(jni::CallStaticMethod<jni::jobject*>(env, *javaClass, *fromLngLats, shape.get()));
}
/**
* MultiPolygon (double[][][][]) -> [[[D + Object array == [[[[D
+ *
+ * static MultiPolygon fromLngLats(List<List<List<Point>>> points)
*/
jni::jobject* operator()(const mapbox::geometry::multi_polygon<T> &geometry) const {
- static jni::jclass* listClass = jni::NewGlobalRef(env, &jni::FindClass(env, "[[[D")).release();
- jni::LocalObject<jni::jarray<jni::jobject>> jarray = jni::NewLocalObject(env, &jni::NewObjectArray(env, geometry.size(), *listClass));
+ // ArrayList
+ static jni::jclass* arrayListClass = jni::NewGlobalRef(env, &jni::FindClass(env, "java/util/ArrayList")).release();
+ static jni::jmethodID* constructor = &jni::GetMethodID(env, *arrayListClass, "<init>", "(I)V");
+ static jni::jmethodID* add = &jni::GetMethodID(env, *arrayListClass, "add", "(ILjava/lang/Object;)V");
+ jni::jobject* arrayList = &jni::NewObject(env, *arrayListClass, *constructor, geometry.size());
for(size_t i = 0; i < geometry.size(); i = i + 1) {
- jni::LocalObject<jni::jarray<jni::jobject>> shape = jni::NewLocalObject(env, toShape<>(env, geometry.at(i)));
- jni::SetObjectArrayElement(env, *jarray, i, shape.get());
+ jni::LocalObject<jni::jobject> shape = jni::NewLocalObject(env, toShape<>(env, geometry.at(i)));
+ jni::CallMethod<void>(env, arrayList, *add, i, shape.get());
}
// Create the MultiPolygon
- static jni::jclass* javaClass = jni::NewGlobalRef(env, &jni::FindClass(env, "com/mapbox/services/commons/geojson/MultiPolygon")).release();
- static jni::jmethodID* fromGeometries = &jni::GetStaticMethodID(env, *javaClass, "fromCoordinates", "([[[[D)Lcom/mapbox/services/commons/geojson/MultiPolygon;");
- return reinterpret_cast<jni::jobject*>(jni::CallStaticMethod<jni::jobject*>(env, *javaClass, *fromGeometries, jarray.get()));
+ static jni::jclass* javaClass = jni::NewGlobalRef(env, &jni::FindClass(env, "com/mapbox/geojson/MultiPolygon")).release();
+ static jni::jmethodID* fromGeometries = &jni::GetStaticMethodID(env, *javaClass, "fromLngLats", "(Ljava/util/List;)Lcom/mapbox/geojson/MultiPolygon;");
+ return reinterpret_cast<jni::jobject*>(jni::CallStaticMethod<jni::jobject*>(env, *javaClass, *fromGeometries, arrayList));
}
/**
* GeometryCollection
*/
jni::jobject* operator()(const mapbox::geometry::geometry_collection<T> &collection) const {
- static jni::jclass* geometryClass = jni::NewGlobalRef(env, &jni::FindClass(env, "com/mapbox/services/commons/geojson/Geometry")).release();
+ static jni::jclass* geometryClass = jni::NewGlobalRef(env, &jni::FindClass(env, "com/mapbox/geojson/Geometry")).release();
jni::LocalObject<jni::jarray<jni::jobject>> jarray = jni::NewLocalObject(env, &jni::NewObjectArray(env, collection.size(), *geometryClass));
for(size_t i = 0; i < collection.size(); i = i + 1) {
@@ -112,8 +115,8 @@ public:
}
// Turn into array list and create the GeometryCollection
- static jni::jclass* javaClass = jni::NewGlobalRef(env, &jni::FindClass(env, "com/mapbox/services/commons/geojson/GeometryCollection")).release();
- static jni::jmethodID* fromGeometries = &jni::GetStaticMethodID(env, *javaClass, "fromGeometries", "(Ljava/util/List;)Lcom/mapbox/services/commons/geojson/GeometryCollection;");
+ static jni::jclass* javaClass = jni::NewGlobalRef(env, &jni::FindClass(env, "com/mapbox/geojson/GeometryCollection")).release();
+ static jni::jmethodID* fromGeometries = &jni::GetStaticMethodID(env, *javaClass, "fromGeometries", "(Ljava/util/List;)Lcom/mapbox/geojson/GeometryCollection;");
jni::LocalObject<jni::jobject> list = jni::NewLocalObject(env, toArrayList<>(env, *jarray));
return reinterpret_cast<jni::jobject*>(jni::CallStaticMethod<jni::jobject*>(env, *javaClass, *fromGeometries, list.get()));
@@ -122,47 +125,50 @@ public:
private:
/**
- * x, y -> jarray<jdouble> ([x,y])
- */
- static jni::jarray<jni::jdouble>* toGeoJsonPosition(JNIEnv& env, double x, double y) {
- jni::jarray<jni::jdouble>& jarray = jni::NewArray<jni::jdouble>(env, 2);
- jni::jdouble array[] = {x, y};
- jni::SetArrayRegion(env, jarray, 0, 2, array);
- return &jarray;
- }
+ * vector<point<T>> -> List<Point>
+ */
+ static jni::jobject* toGeoJsonListOfPoints(JNIEnv& env, std::vector<mapbox::geometry::point<T>> points) {
- /**
- * vector<point<T>> -> jarray<jobject> (double[][]) -> [D + Object array == [[D
- */
- static jni::jarray<jni::jobject>* toGeoJsonCoordinates(JNIEnv& env, std::vector<mapbox::geometry::point<T>> points) {
- static jni::jclass* javaClass = jni::NewGlobalRef(env, &jni::FindClass(env, "[D")).release();
- jni::jarray<jni::jobject>& jarray = jni::NewObjectArray(env, points.size(), *javaClass);
+ // ArrayList
+ static jni::jclass* javaClass = jni::NewGlobalRef(env, &jni::FindClass(env, "java/util/ArrayList")).release();
+ static jni::jmethodID* constructor = &jni::GetMethodID(env, *javaClass, "<init>", "(I)V");
+ static jni::jmethodID* add = &jni::GetMethodID(env, *javaClass, "add", "(ILjava/lang/Object;)V");
+ jni::jobject* arrayList = &jni::NewObject(env, *javaClass, *constructor, points.size());
+
+
+ // Point
+ static jni::jclass* pointJavaClass = jni::NewGlobalRef(env, &jni::FindClass(env, "com/mapbox/geojson/Point")).release();
+ static jni::jmethodID* fromLngLat = &jni::GetStaticMethodID(env, *pointJavaClass, "fromLngLat", "(DD)Lcom/mapbox/geojson/Point;");
for(size_t i = 0; i < points.size(); i = i + 1) {
mapbox::geometry::point<T> point = points.at(i);
- jni::LocalObject<jni::jarray<jni::jdouble>> position = jni::NewLocalObject(env, toGeoJsonPosition(env, point.x, point.y));
- jni::SetObjectArrayElement(env, jarray, i, position.get());
+ jni::LocalObject<jni::jobject> pointObject =
+ jni::NewLocalObject(env, jni::CallStaticMethod<jni::jobject*>(env, *pointJavaClass, *fromLngLat, point.x, point.y));
+ jni::CallMethod<void>(env, arrayList, *add, i, pointObject.get());
}
- return &jarray;
+ return arrayList;
}
/**
- * polygon<T>
- * multi_line_string<T>
- * -> jarray<jobject> (double[][][]) -> [[D + Object array == [[[D
+ * geometry -> List<List<Point>>
*/
template <class SHAPE>
- static jni::jarray<jni::jobject>* toShape(JNIEnv& env, SHAPE value) {
- static jni::jclass* javaClass = jni::NewGlobalRef(env, &jni::FindClass(env, "[[D")).release();
- jni::jarray<jni::jobject>& jarray = jni::NewObjectArray(env, value.size(), *javaClass);
+ static jni::jobject* toShape(JNIEnv& env, SHAPE value) {
+
+ // ArrayList
+ static jni::jclass* javaClass = jni::NewGlobalRef(env, &jni::FindClass(env, "java/util/ArrayList")).release();
+ static jni::jmethodID* constructor = &jni::GetMethodID(env, *javaClass, "<init>", "(I)V");
+ static jni::jmethodID* add = &jni::GetMethodID(env, *javaClass, "add", "(ILjava/lang/Object;)V");
+ jni::jobject* arrayList = &jni::NewObject(env, *javaClass, *constructor, value.size());
+
for(size_t i = 0; i < value.size(); i = i + 1) {
- jni::LocalObject<jni::jarray<jni::jobject>> coordinates = jni::NewLocalObject(env, toGeoJsonCoordinates(env, value.at(i)));
- jni::SetObjectArrayElement(env, jarray, i, coordinates.get());
+ jni::LocalObject<jni::jobject> listOfPoints = jni::NewLocalObject(env, toGeoJsonListOfPoints(env, value.at(i)));
+ jni::CallMethod<void>(env, arrayList, *add, i, listOfPoints.get());
}
- return &jarray;
+ return arrayList;
}
};
diff --git a/platform/android/src/geojson/feature.cpp b/platform/android/src/geojson/feature.cpp
index a6b387cd15..d8a4e829e2 100644
--- a/platform/android/src/geojson/feature.cpp
+++ b/platform/android/src/geojson/feature.cpp
@@ -8,11 +8,11 @@ namespace geojson {
mbgl::Feature Feature::convert(jni::JNIEnv& env, jni::Object<Feature> jFeature) {
// Convert
- auto jGeometry = getGeometry(env, jFeature);
- auto jProperties = Feature::getProperties(env, jFeature);
+ auto jGeometry = geometry(env, jFeature);
+ auto jProperties = Feature::properties(env, jFeature);
std::experimental::optional<mapbox::geometry::identifier> id;
- auto jId = Feature::getId(env, jFeature);
+ auto jId = Feature::id(env, jFeature);
if (jId) {
id = { jni::Make<std::string>(env, jId) };
}
@@ -31,18 +31,18 @@ mbgl::Feature Feature::convert(jni::JNIEnv& env, jni::Object<Feature> jFeature)
return feature;
}
-jni::Object<Geometry> Feature::getGeometry(jni::JNIEnv& env, jni::Object<Feature> jFeature) {
- static auto method = Feature::javaClass.GetMethod<jni::Object<Geometry> ()>(env, "getGeometry");
+jni::Object<Geometry> Feature::geometry(jni::JNIEnv& env, jni::Object<Feature> jFeature) {
+ static auto method = Feature::javaClass.GetMethod<jni::Object<Geometry> ()>(env, "geometry");
return jFeature.Call(env, method);
}
-jni::Object<gson::JsonObject> Feature::getProperties(jni::JNIEnv& env, jni::Object<Feature> jFeature) {
- static auto method = Feature::javaClass.GetMethod<jni::Object<gson::JsonObject> ()>(env, "getProperties");
+jni::Object<gson::JsonObject> Feature::properties(jni::JNIEnv& env, jni::Object<Feature> jFeature) {
+ static auto method = Feature::javaClass.GetMethod<jni::Object<gson::JsonObject> ()>(env, "properties");
return jFeature.Call(env, method);
}
-jni::String Feature::getId(jni::JNIEnv& env, jni::Object<Feature> jFeature) {
- static auto method = Feature::javaClass.GetMethod<jni::String ()>(env, "getId");
+jni::String Feature::id(jni::JNIEnv& env, jni::Object<Feature> jFeature) {
+ static auto method = Feature::javaClass.GetMethod<jni::String ()>(env, "id");
return jFeature.Call(env, method);
}
diff --git a/platform/android/src/geojson/feature.hpp b/platform/android/src/geojson/feature.hpp
index b5d856cc42..ab59d783e5 100644
--- a/platform/android/src/geojson/feature.hpp
+++ b/platform/android/src/geojson/feature.hpp
@@ -16,17 +16,17 @@ namespace geojson {
class Feature : private mbgl::util::noncopyable {
public:
- static constexpr auto Name() { return "com/mapbox/services/commons/geojson/Feature"; };
+ static constexpr auto Name() { return "com/mapbox/geojson/Feature"; };
static jni::Object<Feature> fromGeometry(jni::JNIEnv&, jni::Object<Geometry>, jni::Object<gson::JsonObject>, jni::String);
static mbgl::Feature convert(jni::JNIEnv&, jni::Object<Feature>);
- static jni::Object<Geometry> getGeometry(jni::JNIEnv&, jni::Object<Feature>);
+ static jni::Object<Geometry> geometry(jni::JNIEnv&, jni::Object<Feature>);
- static jni::String getId(jni::JNIEnv&, jni::Object<Feature>);
+ static jni::String id(jni::JNIEnv&, jni::Object<Feature>);
- static jni::Object<gson::JsonObject> getProperties(jni::JNIEnv&, jni::Object<Feature>);
+ static jni::Object<gson::JsonObject> properties(jni::JNIEnv&, jni::Object<Feature>);
static jni::Class<Feature> javaClass;
diff --git a/platform/android/src/geojson/feature_collection.cpp b/platform/android/src/geojson/feature_collection.cpp
index 2f156532ae..18a41d48fa 100644
--- a/platform/android/src/geojson/feature_collection.cpp
+++ b/platform/android/src/geojson/feature_collection.cpp
@@ -7,24 +7,28 @@ namespace android {
namespace geojson {
mbgl::FeatureCollection FeatureCollection::convert(jni::JNIEnv& env, jni::Object<FeatureCollection> jCollection) {
- auto jFeatureList = FeatureCollection::getFeatures(env, jCollection);
- auto jFeatures = java::util::List::toArray<Feature>(env, jFeatureList);
- auto size = size_t(jFeatures.Length(env));
-
auto collection = mbgl::FeatureCollection();
- collection.reserve(size);
- for (size_t i = 0; i < size; i++) {
- auto jFeature = jFeatures.Get(env, i);
- collection.push_back(Feature::convert(env, jFeature));
- jni::DeleteLocalRef(env, jFeature);
- }
+ if (jCollection) {
+ auto jFeatureList = FeatureCollection::features(env, jCollection);
+ auto jFeatures = java::util::List::toArray<Feature>(env, jFeatureList);
+ auto size = size_t(jFeatures.Length(env));
+ collection.reserve(size);
+ for (size_t i = 0; i < size; i++) {
+ auto jFeature = jFeatures.Get(env, i);
+ collection.push_back(Feature::convert(env, jFeature));
+ jni::DeleteLocalRef(env, jFeature);
+ }
+
+ jni::DeleteLocalRef(env, jFeatures);
+ jni::DeleteLocalRef(env, jFeatureList);
+ }
return collection;
}
-jni::Object<java::util::List> FeatureCollection::getFeatures(jni::JNIEnv& env, jni::Object<FeatureCollection> jCollection) {
- static auto method = FeatureCollection::javaClass.GetMethod<jni::Object<java::util::List> ()>(env, "getFeatures");
+jni::Object<java::util::List> FeatureCollection::features(jni::JNIEnv& env, jni::Object<FeatureCollection> jCollection) {
+ static auto method = FeatureCollection::javaClass.GetMethod<jni::Object<java::util::List> ()>(env, "features");
return jCollection.Call(env, method);
}
diff --git a/platform/android/src/geojson/feature_collection.hpp b/platform/android/src/geojson/feature_collection.hpp
index 8e9717e82b..259ffab370 100644
--- a/platform/android/src/geojson/feature_collection.hpp
+++ b/platform/android/src/geojson/feature_collection.hpp
@@ -13,11 +13,11 @@ namespace geojson {
class FeatureCollection : private mbgl::util::noncopyable {
public:
- static constexpr auto Name() { return "com/mapbox/services/commons/geojson/FeatureCollection"; };
+ static constexpr auto Name() { return "com/mapbox/geojson/FeatureCollection"; };
static mbgl::FeatureCollection convert(jni::JNIEnv&, jni::Object<FeatureCollection>);
- static jni::Object<java::util::List> getFeatures(jni::JNIEnv&, jni::Object<FeatureCollection>);
+ static jni::Object<java::util::List> features(jni::JNIEnv&, jni::Object<FeatureCollection>);
static jni::Class<FeatureCollection> javaClass;
diff --git a/platform/android/src/geojson/geometry.cpp b/platform/android/src/geojson/geometry.cpp
index 33bb4ee3db..ca19d8fb03 100644
--- a/platform/android/src/geojson/geometry.cpp
+++ b/platform/android/src/geojson/geometry.cpp
@@ -33,7 +33,7 @@ mapbox::geojson::geometry Geometry::convert(jni::JNIEnv &env, jni::Object<Geomet
}
std::string Geometry::getType(jni::JNIEnv &env, jni::Object<Geometry> jGeometry) {
- static auto method = Geometry::javaClass.GetMethod<jni::String ()>(env, "getType");
+ static auto method = Geometry::javaClass.GetMethod<jni::String ()>(env, "type");
auto jType = jGeometry.Call(env, method);
auto type = jni::Make<std::string>(env, jType);
jni::DeleteLocalRef(env, jType);
diff --git a/platform/android/src/geojson/geometry.hpp b/platform/android/src/geojson/geometry.hpp
index bdcff6bb3e..b7f8909f6f 100644
--- a/platform/android/src/geojson/geometry.hpp
+++ b/platform/android/src/geojson/geometry.hpp
@@ -11,7 +11,7 @@ namespace geojson {
class Geometry : private mbgl::util::noncopyable {
public:
- static constexpr auto Name() { return "com/mapbox/services/commons/geojson/Geometry"; };
+ static constexpr auto Name() { return "com/mapbox/geojson/Geometry"; };
static mapbox::geojson::geometry convert(jni::JNIEnv&, jni::Object<Geometry>);
diff --git a/platform/android/src/geojson/line_string.cpp b/platform/android/src/geojson/line_string.cpp
index d0719f2538..8eebd53550 100644
--- a/platform/android/src/geojson/line_string.cpp
+++ b/platform/android/src/geojson/line_string.cpp
@@ -1,6 +1,6 @@
#include "line_string.hpp"
-#include "position.hpp"
+#include "point.hpp"
namespace mbgl {
namespace android {
@@ -10,35 +10,36 @@ mapbox::geojson::line_string LineString::convert(jni::JNIEnv &env, jni::Object<L
mapbox::geojson::line_string lineString;
if (jLineString) {
- auto jPositionList = LineString::getCoordinates(env, jLineString);
- lineString = LineString::convert(env, jPositionList);
- jni::DeleteLocalRef(env, jPositionList);
+ auto jPointList = LineString::coordinates(env, jLineString);
+ lineString = LineString::convert(env, jPointList);
+ jni::DeleteLocalRef(env, jPointList);
}
return lineString;
}
-mapbox::geojson::line_string LineString::convert(jni::JNIEnv &env, jni::Object<java::util::List/*<Position>*/> jPositionList) {
+mapbox::geojson::line_string LineString::convert(jni::JNIEnv &env, jni::Object<java::util::List/*<Point>*/> jPointList) {
mapbox::geojson::line_string lineString;
- if (jPositionList) {
- auto jPositionArray = java::util::List::toArray<Position>(env, jPositionList);
+ if (jPointList) {
+ auto jPointArray = java::util::List::toArray<Point>(env, jPointList);
+ auto size = jPointArray.Length(env);
+ lineString.reserve(size);
- auto size = jPositionArray.Length(env);
for (std::size_t i = 0; i < size; i++) {
- auto jPosition = jPositionArray.Get(env, i);
- lineString.push_back(Position::convert(env, jPosition));
- jni::DeleteLocalRef(env, jPosition);
+ auto jPoint = jPointArray.Get(env, i);
+ lineString.push_back(Point::convert(env, jPoint));
+ jni::DeleteLocalRef(env, jPoint);
}
- jni::DeleteLocalRef(env, jPositionArray);
+ jni::DeleteLocalRef(env, jPointArray);
}
return lineString;
}
-jni::Object<java::util::List> LineString::getCoordinates(jni::JNIEnv &env, jni::Object<LineString> jLineString) {
- static auto method = LineString::javaClass.GetMethod<jni::Object<java::util::List> ()>(env, "getCoordinates");
+jni::Object<java::util::List> LineString::coordinates(jni::JNIEnv &env, jni::Object<LineString> jLineString) {
+ static auto method = LineString::javaClass.GetMethod<jni::Object<java::util::List> ()>(env, "coordinates");
return jLineString.Call(env, method);
}
diff --git a/platform/android/src/geojson/line_string.hpp b/platform/android/src/geojson/line_string.hpp
index d3be68d0a5..86033c2e6a 100644
--- a/platform/android/src/geojson/line_string.hpp
+++ b/platform/android/src/geojson/line_string.hpp
@@ -14,15 +14,15 @@ namespace geojson {
class LineString : private mbgl::util::noncopyable {
public:
- static constexpr auto Name() { return "com/mapbox/services/commons/geojson/LineString"; };
+ static constexpr auto Name() { return "com/mapbox/geojson/LineString"; };
static constexpr auto Type() { return "LineString"; };
static mapbox::geojson::line_string convert(jni::JNIEnv&, jni::Object<LineString>);
- static mapbox::geojson::line_string convert(jni::JNIEnv&, jni::Object<java::util::List/*<Position>*/>);
+ static mapbox::geojson::line_string convert(jni::JNIEnv&, jni::Object<java::util::List/*<Point>*/>);
- static jni::Object<java::util::List> getCoordinates(jni::JNIEnv&, jni::Object<LineString>);
+ static jni::Object<java::util::List> coordinates(jni::JNIEnv&, jni::Object<LineString>);
static jni::Class<LineString> javaClass;
diff --git a/platform/android/src/geojson/multi_line_string.cpp b/platform/android/src/geojson/multi_line_string.cpp
index b676144bf5..c748d4786f 100644
--- a/platform/android/src/geojson/multi_line_string.cpp
+++ b/platform/android/src/geojson/multi_line_string.cpp
@@ -10,27 +10,27 @@ mapbox::geojson::multi_line_string MultiLineString::convert(jni::JNIEnv &env, jn
mapbox::geojson::multi_line_string multiLineString;
if (jMultiLineString) {
- auto jPositionListsList = MultiLineString::getCoordinates(env, jMultiLineString);
- multiLineString = MultiLineString::convert(env, jPositionListsList);
- jni::DeleteLocalRef(env, jPositionListsList);
+ auto jPointListsList = MultiLineString::coordinates(env, jMultiLineString);
+ multiLineString = MultiLineString::convert(env, jPointListsList);
+ jni::DeleteLocalRef(env, jPointListsList);
}
return multiLineString;
}
-mapbox::geojson::multi_line_string MultiLineString::convert(jni::JNIEnv &env, jni::Object<java::util::List/*<java::util::List<Position>>*/> jPositionListsList) {
+mapbox::geojson::multi_line_string MultiLineString::convert(jni::JNIEnv &env, jni::Object<java::util::List/*<java::util::List<Point>>*/> jPointListsList) {
mapbox::geojson::multi_line_string multiLineString;
- if (jPositionListsList) {
- auto jPositionListsArray = java::util::List::toArray<java::util::List>(env, jPositionListsList);
+ if (jPointListsList) {
+ auto jPositionListsArray = java::util::List::toArray<java::util::List>(env, jPointListsList);
auto size = jPositionListsArray.Length(env);
multiLineString.reserve(size);
for (std::size_t i = 0; i < size; i++) {
- auto jPositionList = jPositionListsArray.Get(env, i);
- multiLineString.push_back(LineString::convert(env, jPositionList));
- jni::DeleteLocalRef(env, jPositionList);
+ auto jPointsList = jPositionListsArray.Get(env, i);
+ multiLineString.push_back(LineString::convert(env, jPointsList));
+ jni::DeleteLocalRef(env, jPointsList);
}
jni::DeleteLocalRef(env, jPositionListsArray);
@@ -39,8 +39,8 @@ mapbox::geojson::multi_line_string MultiLineString::convert(jni::JNIEnv &env, jn
return multiLineString;
}
-jni::Object<java::util::List> MultiLineString::getCoordinates(jni::JNIEnv &env, jni::Object<MultiLineString> jLineString) {
- static auto method = MultiLineString::javaClass.GetMethod<jni::Object<java::util::List> ()>(env, "getCoordinates");
+jni::Object<java::util::List> MultiLineString::coordinates(jni::JNIEnv &env, jni::Object<MultiLineString> jLineString) {
+ static auto method = MultiLineString::javaClass.GetMethod<jni::Object<java::util::List> ()>(env, "coordinates");
return jLineString.Call(env, method);
}
diff --git a/platform/android/src/geojson/multi_line_string.hpp b/platform/android/src/geojson/multi_line_string.hpp
index af33fe72d6..358a6b5dda 100644
--- a/platform/android/src/geojson/multi_line_string.hpp
+++ b/platform/android/src/geojson/multi_line_string.hpp
@@ -13,15 +13,15 @@ namespace geojson {
class MultiLineString : private mbgl::util::noncopyable {
public:
- static constexpr auto Name() { return "com/mapbox/services/commons/geojson/MultiLineString"; };
+ static constexpr auto Name() { return "com/mapbox/geojson/MultiLineString"; };
static constexpr auto Type() { return "MultiLineString"; };
static mapbox::geojson::multi_line_string convert(jni::JNIEnv&, jni::Object<MultiLineString>);
- static mapbox::geojson::multi_line_string convert(jni::JNIEnv&, jni::Object<java::util::List/*<java::util::List<Position>>*/>);
+ static mapbox::geojson::multi_line_string convert(jni::JNIEnv&, jni::Object<java::util::List/*<java::util::List<Point>>*/>);
- static jni::Object<java::util::List> getCoordinates(jni::JNIEnv&, jni::Object<MultiLineString>);
+ static jni::Object<java::util::List> coordinates(jni::JNIEnv&, jni::Object<MultiLineString>);
static jni::Class<MultiLineString> javaClass;
diff --git a/platform/android/src/geojson/multi_point.cpp b/platform/android/src/geojson/multi_point.cpp
index f3acdb1ea6..4f9ff596b2 100644
--- a/platform/android/src/geojson/multi_point.cpp
+++ b/platform/android/src/geojson/multi_point.cpp
@@ -12,16 +12,16 @@ mapbox::geojson::multi_point MultiPoint::convert(jni::JNIEnv &env, jni::Object<M
mapbox::geojson::multi_point multiPoint;
if (jMultiPoint) {
- auto jPositionListsList = MultiPoint::getCoordinates(env, jMultiPoint);
- multiPoint = convertExplicit<mapbox::geojson::multi_point>(LineString::convert(env, jPositionListsList));
- jni::DeleteLocalRef(env, jPositionListsList);
+ auto jPointListsList = MultiPoint::coordinates(env, jMultiPoint);
+ multiPoint = convertExplicit<mapbox::geojson::multi_point>(LineString::convert(env, jPointListsList));
+ jni::DeleteLocalRef(env, jPointListsList);
}
return multiPoint;
}
-jni::Object<java::util::List> MultiPoint::getCoordinates(jni::JNIEnv &env, jni::Object<MultiPoint> jMultiPoint) {
- static auto method = MultiPoint::javaClass.GetMethod<jni::Object<java::util::List> ()>(env, "getCoordinates");
+jni::Object<java::util::List> MultiPoint::coordinates(jni::JNIEnv &env, jni::Object<MultiPoint> jMultiPoint) {
+ static auto method = MultiPoint::javaClass.GetMethod<jni::Object<java::util::List> ()>(env, "coordinates");
return jMultiPoint.Call(env, method);
}
diff --git a/platform/android/src/geojson/multi_point.hpp b/platform/android/src/geojson/multi_point.hpp
index 7a698287eb..e893e879af 100644
--- a/platform/android/src/geojson/multi_point.hpp
+++ b/platform/android/src/geojson/multi_point.hpp
@@ -13,13 +13,13 @@ namespace geojson {
class MultiPoint : private mbgl::util::noncopyable {
public:
- static constexpr auto Name() { return "com/mapbox/services/commons/geojson/MultiPoint"; };
+ static constexpr auto Name() { return "com/mapbox/geojson/MultiPoint"; };
static constexpr auto Type() { return "MultiPoint"; };
static mapbox::geojson::multi_point convert(jni::JNIEnv&, jni::Object<MultiPoint>);
- static jni::Object<java::util::List> getCoordinates(jni::JNIEnv&, jni::Object<MultiPoint>);
+ static jni::Object<java::util::List> coordinates(jni::JNIEnv&, jni::Object<MultiPoint>);
static jni::Class<MultiPoint> javaClass;
diff --git a/platform/android/src/geojson/multi_polygon.cpp b/platform/android/src/geojson/multi_polygon.cpp
index a55884a110..aadba8c8a6 100644
--- a/platform/android/src/geojson/multi_polygon.cpp
+++ b/platform/android/src/geojson/multi_polygon.cpp
@@ -10,27 +10,27 @@ mapbox::geojson::multi_polygon MultiPolygon::convert(jni::JNIEnv &env, jni::Obje
mapbox::geojson::multi_polygon multiPolygon;
if (jMultiPolygon) {
- auto jPositionListsListList = MultiPolygon::getCoordinates(env, jMultiPolygon);
- auto jPositionListsListArray = java::util::List::toArray<java::util::List>(env, jPositionListsListList);
+ auto jPointListsListList = MultiPolygon::coordinates(env, jMultiPolygon);
+ auto jPointListsListArray = java::util::List::toArray<java::util::List>(env, jPointListsListList);
- auto size = jPositionListsListArray.Length(env);
+ auto size = jPointListsListArray.Length(env);
multiPolygon.reserve(size);
for (size_t i = 0; i < size; i++) {
- auto jPositionListsList = jPositionListsListArray.Get(env, i);
+ auto jPositionListsList = jPointListsListArray.Get(env, i);
multiPolygon.push_back(Polygon::convert(env, jPositionListsList));
jni::DeleteLocalRef(env, jPositionListsList);
}
- jni::DeleteLocalRef(env, jPositionListsListList);
- jni::DeleteLocalRef(env, jPositionListsListArray);
+ jni::DeleteLocalRef(env, jPointListsListArray);
+ jni::DeleteLocalRef(env, jPointListsListList);
}
return multiPolygon;
}
-jni::Object<java::util::List> MultiPolygon::getCoordinates(jni::JNIEnv &env, jni::Object<MultiPolygon> jPolygon) {
- static auto method = MultiPolygon::javaClass.GetMethod<jni::Object<java::util::List> ()>(env, "getCoordinates");
+jni::Object<java::util::List> MultiPolygon::coordinates(jni::JNIEnv &env, jni::Object<MultiPolygon> jPolygon) {
+ static auto method = MultiPolygon::javaClass.GetMethod<jni::Object<java::util::List> ()>(env, "coordinates");
return jPolygon.Call(env, method);
}
diff --git a/platform/android/src/geojson/multi_polygon.hpp b/platform/android/src/geojson/multi_polygon.hpp
index 1f144cffd2..6e1dfacfc8 100644
--- a/platform/android/src/geojson/multi_polygon.hpp
+++ b/platform/android/src/geojson/multi_polygon.hpp
@@ -13,13 +13,13 @@ namespace geojson {
class MultiPolygon : private mbgl::util::noncopyable {
public:
- static constexpr auto Name() { return "com/mapbox/services/commons/geojson/MultiPolygon"; };
+ static constexpr auto Name() { return "com/mapbox/geojson/MultiPolygon"; };
static constexpr auto Type() { return "MultiPolygon"; };
static mapbox::geojson::multi_polygon convert(jni::JNIEnv&, jni::Object<MultiPolygon>);
- static jni::Object<java::util::List> getCoordinates(jni::JNIEnv&, jni::Object<MultiPolygon>);
+ static jni::Object<java::util::List> coordinates(jni::JNIEnv&, jni::Object<MultiPolygon>);
static jni::Class<MultiPolygon> javaClass;
diff --git a/platform/android/src/geojson/point.cpp b/platform/android/src/geojson/point.cpp
index 3d19a119d7..e95376cd2e 100644
--- a/platform/android/src/geojson/point.cpp
+++ b/platform/android/src/geojson/point.cpp
@@ -1,19 +1,48 @@
#include "point.hpp"
+#include "../java/util.hpp"
+#include "../java_types.hpp"
+#include "../style/value.hpp"
namespace mbgl {
namespace android {
namespace geojson {
mapbox::geojson::point Point::convert(jni::JNIEnv &env, jni::Object<Point> jPoint) {
- auto jPosition = Point::getPosition(env, jPoint);
- auto point = Position::convert(env, jPosition);
- jni::DeleteLocalRef(env, jPosition);
+ mapbox::geojson::point point;
+
+ if (jPoint) {
+ auto jDoubleList = Point::coordinates(env, jPoint);
+ point = Point::convert(env, jDoubleList);
+ jni::DeleteLocalRef(env, jDoubleList);
+ }
+
+ return point;
+}
+
+mapbox::geojson::point Point::convert(jni::JNIEnv &env, jni::Object<java::util::List/*<Double>*/> jDoubleList) {
+ mapbox::geojson::point point;
+
+ if (jDoubleList) {
+ auto jDoubleArray = java::util::List::toArray<jobject>(env, jDoubleList);
+
+ auto lonObject = jDoubleArray.Get(env, 0);
+ auto latObject = jDoubleArray.Get(env, 1);
+
+ point.x = jni::CallMethod<jni::jdouble>(env, lonObject,
+ *java::Number::doubleValueMethodId);
+ point.y = jni::CallMethod<jni::jdouble>(env, latObject,
+ *java::Number::doubleValueMethodId);
+
+ jni::DeleteLocalRef(env, lonObject);
+ jni::DeleteLocalRef(env, latObject);
+ jni::DeleteLocalRef(env, jDoubleArray);
+ }
return point;
}
-jni::Object<Position> Point::getPosition(JNIEnv& env, jni::Object<Point> jPoint) {
- static auto method = Point::javaClass.GetMethod<jni::Object<Position> ()>(env, "getCoordinates");
- return jPoint.Call(env, method);
+jni::Object<java::util::List> Point::coordinates(jni::JNIEnv &env, jni::Object<Point> jPoint) {
+ static auto method = Point::javaClass.GetMethod<jni::Object<java::util::List> ()>(env, "coordinates");
+ return jPoint.Call(env, method);
}
void Point::registerNative(jni::JNIEnv &env) {
diff --git a/platform/android/src/geojson/point.hpp b/platform/android/src/geojson/point.hpp
index 64ac0af9cc..c6412299bf 100644
--- a/platform/android/src/geojson/point.hpp
+++ b/platform/android/src/geojson/point.hpp
@@ -3,23 +3,25 @@
#include <mbgl/util/geojson.hpp>
#include <mbgl/util/noncopyable.hpp>
-#include "position.hpp"
-
#include <jni/jni.hpp>
+#include "../java/util.hpp"
+
namespace mbgl {
namespace android {
namespace geojson {
class Point : private mbgl::util::noncopyable {
public:
- static constexpr auto Name() { return "com/mapbox/services/commons/geojson/Point"; };
+ static constexpr auto Name() { return "com/mapbox/geojson/Point"; };
static constexpr auto Type() { return "Point"; };
static mapbox::geojson::point convert(jni::JNIEnv&, jni::Object<Point>);
- static jni::Object<Position> getPosition(JNIEnv&, jni::Object<Point>);
+ static mapbox::geojson::point convert(jni::JNIEnv&, jni::Object<java::util::List/*<Double>*/>);
+
+ static jni::Object<java::util::List> coordinates(JNIEnv&, jni::Object<Point>);
static jni::Class<Point> javaClass;
diff --git a/platform/android/src/geojson/polygon.cpp b/platform/android/src/geojson/polygon.cpp
index ef00f9df3f..30ba996640 100644
--- a/platform/android/src/geojson/polygon.cpp
+++ b/platform/android/src/geojson/polygon.cpp
@@ -12,7 +12,7 @@ mapbox::geojson::polygon Polygon::convert(jni::JNIEnv &env, jni::Object<Polygon>
mapbox::geojson::polygon polygon;
if (jPolygon) {
- auto jPositionListsList = Polygon::getCoordinates(env, jPolygon);
+ auto jPositionListsList = Polygon::coordinates(env, jPolygon);
polygon = Polygon::convert(env, jPositionListsList);
jni::DeleteLocalRef(env, jPositionListsList);
}
@@ -20,11 +20,11 @@ mapbox::geojson::polygon Polygon::convert(jni::JNIEnv &env, jni::Object<Polygon>
return polygon;
}
-mapbox::geojson::polygon Polygon::convert(jni::JNIEnv &env, jni::Object<java::util::List/*<java::util::List<Position>>*/> jPositionListsList) {
+mapbox::geojson::polygon Polygon::convert(jni::JNIEnv &env, jni::Object<java::util::List/*<java::util::List<Point>>*/> jPointListsList) {
mapbox::geojson::polygon polygon;
- if (jPositionListsList) {
- auto multiLine = MultiLineString::convert(env, jPositionListsList);
+ if (jPointListsList) {
+ auto multiLine = MultiLineString::convert(env, jPointListsList);
polygon.reserve(multiLine.size());
for (auto&& line : multiLine) {
polygon.emplace_back(convertExplicit<mapbox::geojson::linear_ring>(std::move(line)));
@@ -35,8 +35,8 @@ mapbox::geojson::polygon Polygon::convert(jni::JNIEnv &env, jni::Object<java::ut
}
-jni::Object<java::util::List> Polygon::getCoordinates(jni::JNIEnv &env, jni::Object<Polygon> jPolygon) {
- static auto method = Polygon::javaClass.GetMethod<jni::Object<java::util::List> ()>(env, "getCoordinates");
+jni::Object<java::util::List> Polygon::coordinates(jni::JNIEnv &env, jni::Object<Polygon> jPolygon) {
+ static auto method = Polygon::javaClass.GetMethod<jni::Object<java::util::List> ()>(env, "coordinates");
return jPolygon.Call(env, method);
}
diff --git a/platform/android/src/geojson/polygon.hpp b/platform/android/src/geojson/polygon.hpp
index e5362cedf1..a8b2b93827 100644
--- a/platform/android/src/geojson/polygon.hpp
+++ b/platform/android/src/geojson/polygon.hpp
@@ -13,15 +13,15 @@ namespace geojson {
class Polygon : private mbgl::util::noncopyable {
public:
- static constexpr auto Name() { return "com/mapbox/services/commons/geojson/Polygon"; };
+ static constexpr auto Name() { return "com/mapbox/geojson/Polygon"; };
static constexpr auto Type() { return "Polygon"; };
static mapbox::geojson::polygon convert(jni::JNIEnv &, jni::Object<Polygon>);
- static mapbox::geojson::polygon convert(jni::JNIEnv&, jni::Object<java::util::List/*<java::util::List<Position>>*/>);
+ static mapbox::geojson::polygon convert(jni::JNIEnv&, jni::Object<java::util::List/*<java::util::List<Point>>*/>);
- static jni::Object<java::util::List> getCoordinates(jni::JNIEnv&, jni::Object<Polygon>);
+ static jni::Object<java::util::List> coordinates(jni::JNIEnv&, jni::Object<Polygon>);
static jni::Class<Polygon> javaClass;
diff --git a/platform/android/src/geojson/position.cpp b/platform/android/src/geojson/position.cpp
deleted file mode 100644
index c0e6da3887..0000000000
--- a/platform/android/src/geojson/position.cpp
+++ /dev/null
@@ -1,27 +0,0 @@
-#include "position.hpp"
-
-namespace mbgl {
-namespace android {
-namespace geojson {
-
-mapbox::geojson::point Position::convert(jni::JNIEnv &env, jni::Object<Position> jPosition) {
- static auto method = Position::javaClass.GetMethod<jni::Array<jdouble> ()>(env, "getCoordinates");
- // Array with 0: longitude, 1: latitude (and optionally 2: altitude)
- auto coordinates = jPosition.Call(env, method);
- jdouble lngLat[2];
- coordinates.GetRegion(env, 0, lngLat);
- mapbox::geojson::point point(lngLat[0], lngLat[1]);
- jni::DeleteLocalRef(env, coordinates);
- return point;
-}
-
-void Position::registerNative(jni::JNIEnv &env) {
- // Lookup the class
- javaClass = *jni::Class<Position>::Find(env).NewGlobalRef(env).release();
-}
-
-jni::Class<Position> Position::javaClass;
-
-} // namespace geojson
-} // namespace android
-} // namespace mbgl \ No newline at end of file
diff --git a/platform/android/src/geojson/position.hpp b/platform/android/src/geojson/position.hpp
deleted file mode 100644
index 7017a8172a..0000000000
--- a/platform/android/src/geojson/position.hpp
+++ /dev/null
@@ -1,27 +0,0 @@
-#pragma once
-
-#include <mbgl/util/geojson.hpp>
-#include <mbgl/util/noncopyable.hpp>
-
-#include <jni/jni.hpp>
-
-namespace mbgl {
-namespace android {
-namespace geojson {
-
-class Position : private mbgl::util::noncopyable {
-public:
- static constexpr auto Name() { return "com/mapbox/services/commons/models/Position"; };
-
- static constexpr auto Type() { return "Position"; };
-
- static mapbox::geojson::point convert(jni::JNIEnv&, jni::Object<Position>);
-
- static jni::Class<Position> javaClass;
-
- static void registerNative(jni::JNIEnv&);
-};
-
-} // namespace geojson
-} // namespace android
-} // namespace mbgl \ No newline at end of file
diff --git a/platform/android/src/jni.cpp b/platform/android/src/jni.cpp
index 88ad0edb9e..c2fd1c95ad 100755
--- a/platform/android/src/jni.cpp
+++ b/platform/android/src/jni.cpp
@@ -20,7 +20,6 @@
#include "geojson/multi_polygon.hpp"
#include "geojson/point.hpp"
#include "geojson/polygon.hpp"
-#include "geojson/position.hpp"
#include "geometry/lat_lng.hpp"
#include "geometry/lat_lng_bounds.hpp"
#include "geometry/lat_lng_quad.hpp"
@@ -128,7 +127,6 @@ void registerNatives(JavaVM *vm) {
geojson::MultiPolygon::registerNative(env);
geojson::Point::registerNative(env);
geojson::Polygon::registerNative(env);
- geojson::Position::registerNative(env);
// Geometry
LatLng::registerNative(env);
diff --git a/platform/android/src/map/camera_position.cpp b/platform/android/src/map/camera_position.cpp
index 1fc5f9789f..01ffc6530b 100644
--- a/platform/android/src/map/camera_position.cpp
+++ b/platform/android/src/map/camera_position.cpp
@@ -33,7 +33,9 @@ mbgl::CameraOptions CameraPosition::getCameraOptions(jni::JNIEnv& env, jni::Obje
static auto tilt = CameraPosition::javaClass.GetField<jni::jdouble>(env, "tilt");
static auto zoom = CameraPosition::javaClass.GetField<jni::jdouble>(env, "zoom");
- auto center = LatLng::getLatLng(env, position.Get(env, target));
+ auto jtarget = position.Get(env, target);
+ auto center = LatLng::getLatLng(env, jtarget);
+ jni::DeleteLocalRef(env, jtarget);
return mbgl::CameraOptions {
center,
diff --git a/platform/android/src/map/image.cpp b/platform/android/src/map/image.cpp
index 5f5c90eddd..52e0e0d255 100644
--- a/platform/android/src/map/image.cpp
+++ b/platform/android/src/map/image.cpp
@@ -16,7 +16,9 @@ mbgl::style::Image Image::getImage(jni::JNIEnv& env, jni::Object<Image> image) {
auto width = image.Get(env, widthField);
auto pixelRatio = image.Get(env, pixelRatioField);
auto pixels = image.Get(env, bufferField);
- auto name = jni::Make<std::string>(env, image.Get(env, nameField));
+ auto jName = image.Get(env, nameField);
+ auto name = jni::Make<std::string>(env, jName);
+ jni::DeleteLocalRef(env, jName);
jni::NullCheck(env, &pixels);
std::size_t size = pixels.Length(env);
diff --git a/platform/android/src/native_map_view.cpp b/platform/android/src/native_map_view.cpp
index 36a73fee35..67fc132204 100755
--- a/platform/android/src/native_map_view.cpp
+++ b/platform/android/src/native_map_view.cpp
@@ -450,7 +450,7 @@ jni::Array<jni::jlong> NativeMapView::addMarkers(jni::JNIEnv& env, jni::Array<jn
}
void NativeMapView::onLowMemory(JNIEnv&) {
- rendererFrontend->onLowMemory();
+ rendererFrontend->reduceMemoryUse();
}
using DebugOptions = mbgl::MapDebugOptions;
diff --git a/platform/android/src/run_loop.cpp b/platform/android/src/run_loop.cpp
index 1d284a9e72..34366d836a 100644
--- a/platform/android/src/run_loop.cpp
+++ b/platform/android/src/run_loop.cpp
@@ -216,11 +216,8 @@ LOOP_HANDLE RunLoop::getLoopHandle() {
return Get()->impl.get();
}
-void RunLoop::push(std::shared_ptr<WorkTask> task) {
- withMutex([&] {
- queue.push(std::move(task));
- impl->wake();
- });
+void RunLoop::wake() {
+ impl->wake();
}
void RunLoop::run() {
diff --git a/platform/android/src/style/layers/heatmap_layer.cpp b/platform/android/src/style/layers/heatmap_layer.cpp
new file mode 100644
index 0000000000..609499ec93
--- /dev/null
+++ b/platform/android/src/style/layers/heatmap_layer.cpp
@@ -0,0 +1,134 @@
+// This file is generated. Edit android/platform/scripts/generate-style-code.js, then run `make android-style-code`.
+
+#include "heatmap_layer.hpp"
+
+#include <string>
+
+#include "../conversion/property_value.hpp"
+#include "../conversion/transition_options.hpp"
+
+namespace mbgl {
+namespace android {
+
+ /**
+ * Creates an owning peer object (for layers not attached to the map) from the JVM side
+ */
+ HeatmapLayer::HeatmapLayer(jni::JNIEnv& env, jni::String layerId, jni::String sourceId)
+ : Layer(env, std::make_unique<mbgl::style::HeatmapLayer>(jni::Make<std::string>(env, layerId), jni::Make<std::string>(env, sourceId))) {
+ }
+
+ /**
+ * Creates a non-owning peer object (for layers currently attached to the map)
+ */
+ HeatmapLayer::HeatmapLayer(mbgl::Map& map, mbgl::style::HeatmapLayer& coreLayer)
+ : Layer(map, coreLayer) {
+ }
+
+ /**
+ * Creates an owning peer object (for layers not attached to the map)
+ */
+ HeatmapLayer::HeatmapLayer(mbgl::Map& map, std::unique_ptr<mbgl::style::HeatmapLayer> coreLayer)
+ : Layer(map, std::move(coreLayer)) {
+ }
+
+ HeatmapLayer::~HeatmapLayer() = default;
+
+ // Property getters
+
+ jni::Object<jni::ObjectTag> HeatmapLayer::getHeatmapRadius(jni::JNIEnv& env) {
+ using namespace mbgl::android::conversion;
+ Result<jni::jobject*> converted = convert<jni::jobject*>(env, layer.as<mbgl::style::HeatmapLayer>()->HeatmapLayer::getHeatmapRadius());
+ return jni::Object<jni::ObjectTag>(*converted);
+ }
+
+ jni::Object<TransitionOptions> HeatmapLayer::getHeatmapRadiusTransition(jni::JNIEnv& env) {
+ using namespace mbgl::android::conversion;
+ mbgl::style::TransitionOptions options = layer.as<mbgl::style::HeatmapLayer>()->HeatmapLayer::getHeatmapRadiusTransition();
+ return *convert<jni::Object<TransitionOptions>>(env, options);
+ }
+
+ void HeatmapLayer::setHeatmapRadiusTransition(jni::JNIEnv&, jlong duration, jlong delay) {
+ mbgl::style::TransitionOptions options;
+ options.duration.emplace(mbgl::Milliseconds(duration));
+ options.delay.emplace(mbgl::Milliseconds(delay));
+ layer.as<mbgl::style::HeatmapLayer>()->HeatmapLayer::setHeatmapRadiusTransition(options);
+ }
+
+ jni::Object<jni::ObjectTag> HeatmapLayer::getHeatmapWeight(jni::JNIEnv& env) {
+ using namespace mbgl::android::conversion;
+ Result<jni::jobject*> converted = convert<jni::jobject*>(env, layer.as<mbgl::style::HeatmapLayer>()->HeatmapLayer::getHeatmapWeight());
+ return jni::Object<jni::ObjectTag>(*converted);
+ }
+
+ jni::Object<jni::ObjectTag> HeatmapLayer::getHeatmapIntensity(jni::JNIEnv& env) {
+ using namespace mbgl::android::conversion;
+ Result<jni::jobject*> converted = convert<jni::jobject*>(env, layer.as<mbgl::style::HeatmapLayer>()->HeatmapLayer::getHeatmapIntensity());
+ return jni::Object<jni::ObjectTag>(*converted);
+ }
+
+ jni::Object<TransitionOptions> HeatmapLayer::getHeatmapIntensityTransition(jni::JNIEnv& env) {
+ using namespace mbgl::android::conversion;
+ mbgl::style::TransitionOptions options = layer.as<mbgl::style::HeatmapLayer>()->HeatmapLayer::getHeatmapIntensityTransition();
+ return *convert<jni::Object<TransitionOptions>>(env, options);
+ }
+
+ void HeatmapLayer::setHeatmapIntensityTransition(jni::JNIEnv&, jlong duration, jlong delay) {
+ mbgl::style::TransitionOptions options;
+ options.duration.emplace(mbgl::Milliseconds(duration));
+ options.delay.emplace(mbgl::Milliseconds(delay));
+ layer.as<mbgl::style::HeatmapLayer>()->HeatmapLayer::setHeatmapIntensityTransition(options);
+ }
+
+ jni::Object<jni::ObjectTag> HeatmapLayer::getHeatmapOpacity(jni::JNIEnv& env) {
+ using namespace mbgl::android::conversion;
+ Result<jni::jobject*> converted = convert<jni::jobject*>(env, layer.as<mbgl::style::HeatmapLayer>()->HeatmapLayer::getHeatmapOpacity());
+ return jni::Object<jni::ObjectTag>(*converted);
+ }
+
+ jni::Object<TransitionOptions> HeatmapLayer::getHeatmapOpacityTransition(jni::JNIEnv& env) {
+ using namespace mbgl::android::conversion;
+ mbgl::style::TransitionOptions options = layer.as<mbgl::style::HeatmapLayer>()->HeatmapLayer::getHeatmapOpacityTransition();
+ return *convert<jni::Object<TransitionOptions>>(env, options);
+ }
+
+ void HeatmapLayer::setHeatmapOpacityTransition(jni::JNIEnv&, jlong duration, jlong delay) {
+ mbgl::style::TransitionOptions options;
+ options.duration.emplace(mbgl::Milliseconds(duration));
+ options.delay.emplace(mbgl::Milliseconds(delay));
+ layer.as<mbgl::style::HeatmapLayer>()->HeatmapLayer::setHeatmapOpacityTransition(options);
+ }
+
+
+ jni::Class<HeatmapLayer> HeatmapLayer::javaClass;
+
+ jni::jobject* HeatmapLayer::createJavaPeer(jni::JNIEnv& env) {
+ static auto constructor = HeatmapLayer::javaClass.template GetConstructor<jni::jlong>(env);
+ return HeatmapLayer::javaClass.New(env, constructor, reinterpret_cast<jni::jlong>(this));
+ }
+
+ void HeatmapLayer::registerNative(jni::JNIEnv& env) {
+ // Lookup the class
+ HeatmapLayer::javaClass = *jni::Class<HeatmapLayer>::Find(env).NewGlobalRef(env).release();
+
+ #define METHOD(MethodPtr, name) jni::MakeNativePeerMethod<decltype(MethodPtr), (MethodPtr)>(name)
+
+ // Register the peer
+ jni::RegisterNativePeer<HeatmapLayer>(
+ env, HeatmapLayer::javaClass, "nativePtr",
+ std::make_unique<HeatmapLayer, JNIEnv&, jni::String, jni::String>,
+ "initialize",
+ "finalize",
+ METHOD(&HeatmapLayer::getHeatmapRadiusTransition, "nativeGetHeatmapRadiusTransition"),
+ METHOD(&HeatmapLayer::setHeatmapRadiusTransition, "nativeSetHeatmapRadiusTransition"),
+ METHOD(&HeatmapLayer::getHeatmapRadius, "nativeGetHeatmapRadius"),
+ METHOD(&HeatmapLayer::getHeatmapWeight, "nativeGetHeatmapWeight"),
+ METHOD(&HeatmapLayer::getHeatmapIntensityTransition, "nativeGetHeatmapIntensityTransition"),
+ METHOD(&HeatmapLayer::setHeatmapIntensityTransition, "nativeSetHeatmapIntensityTransition"),
+ METHOD(&HeatmapLayer::getHeatmapIntensity, "nativeGetHeatmapIntensity"),
+ METHOD(&HeatmapLayer::getHeatmapOpacityTransition, "nativeGetHeatmapOpacityTransition"),
+ METHOD(&HeatmapLayer::setHeatmapOpacityTransition, "nativeSetHeatmapOpacityTransition"),
+ METHOD(&HeatmapLayer::getHeatmapOpacity, "nativeGetHeatmapOpacity"));
+ }
+
+} // namespace android
+} // namespace mbgl
diff --git a/platform/android/src/style/layers/heatmap_layer.hpp b/platform/android/src/style/layers/heatmap_layer.hpp
new file mode 100644
index 0000000000..85f9f0292e
--- /dev/null
+++ b/platform/android/src/style/layers/heatmap_layer.hpp
@@ -0,0 +1,50 @@
+// This file is generated. Edit android/platform/scripts/generate-style-code.js, then run `make android-style-code`.
+
+#pragma once
+
+#include "layer.hpp"
+#include "../transition_options.hpp"
+#include <mbgl/style/layers/heatmap_layer.hpp>
+#include <jni/jni.hpp>
+
+namespace mbgl {
+namespace android {
+
+class HeatmapLayer : public Layer {
+public:
+
+ static constexpr auto Name() { return "com/mapbox/mapboxsdk/style/layers/HeatmapLayer"; };
+
+ static jni::Class<HeatmapLayer> javaClass;
+
+ static void registerNative(jni::JNIEnv&);
+
+ HeatmapLayer(jni::JNIEnv&, jni::String, jni::String);
+
+ HeatmapLayer(mbgl::Map&, mbgl::style::HeatmapLayer&);
+
+ HeatmapLayer(mbgl::Map&, std::unique_ptr<mbgl::style::HeatmapLayer>);
+
+ ~HeatmapLayer();
+
+ // Properties
+
+ jni::Object<jni::ObjectTag> getHeatmapRadius(jni::JNIEnv&);
+ void setHeatmapRadiusTransition(jni::JNIEnv&, jlong duration, jlong delay);
+ jni::Object<TransitionOptions> getHeatmapRadiusTransition(jni::JNIEnv&);
+
+ jni::Object<jni::ObjectTag> getHeatmapWeight(jni::JNIEnv&);
+
+ jni::Object<jni::ObjectTag> getHeatmapIntensity(jni::JNIEnv&);
+ void setHeatmapIntensityTransition(jni::JNIEnv&, jlong duration, jlong delay);
+ jni::Object<TransitionOptions> getHeatmapIntensityTransition(jni::JNIEnv&);
+
+ jni::Object<jni::ObjectTag> getHeatmapOpacity(jni::JNIEnv&);
+ void setHeatmapOpacityTransition(jni::JNIEnv&, jlong duration, jlong delay);
+ jni::Object<TransitionOptions> getHeatmapOpacityTransition(jni::JNIEnv&);
+ jni::jobject* createJavaPeer(jni::JNIEnv&);
+
+}; // class HeatmapLayer
+
+} // namespace android
+} // namespace mbgl
diff --git a/platform/android/src/style/layers/layer.cpp b/platform/android/src/style/layers/layer.cpp
index da1550bdb1..29530879a5 100644
--- a/platform/android/src/style/layers/layer.cpp
+++ b/platform/android/src/style/layers/layer.cpp
@@ -10,6 +10,7 @@
#include <mbgl/style/layers/circle_layer.hpp>
#include <mbgl/style/layers/fill_layer.hpp>
#include <mbgl/style/layers/fill_extrusion_layer.hpp>
+#include <mbgl/style/layers/heatmap_layer.hpp>
#include <mbgl/style/layers/hillshade_layer.hpp>
#include <mbgl/style/layers/line_layer.hpp>
#include <mbgl/style/layers/raster_layer.hpp>
diff --git a/platform/android/src/style/layers/layers.cpp b/platform/android/src/style/layers/layers.cpp
index 5d1d1bbcbf..5df689b45d 100644
--- a/platform/android/src/style/layers/layers.cpp
+++ b/platform/android/src/style/layers/layers.cpp
@@ -5,6 +5,7 @@
#include <mbgl/style/layers/circle_layer.hpp>
#include <mbgl/style/layers/fill_extrusion_layer.hpp>
#include <mbgl/style/layers/fill_layer.hpp>
+#include <mbgl/style/layers/heatmap_layer.hpp>
#include <mbgl/style/layers/hillshade_layer.hpp>
#include <mbgl/style/layers/line_layer.hpp>
#include <mbgl/style/layers/raster_layer.hpp>
@@ -16,6 +17,7 @@
#include "custom_layer.hpp"
#include "fill_extrusion_layer.hpp"
#include "fill_layer.hpp"
+#include "heatmap_layer.hpp"
#include "hillshade_layer.hpp"
#include "line_layer.hpp"
#include "raster_layer.hpp"
@@ -32,6 +34,7 @@ template <> struct PeerType<style::BackgroundLayer> { using Type = android::Back
template <> struct PeerType<style::CircleLayer> { using Type = android::CircleLayer; };
template <> struct PeerType<style::FillExtrusionLayer> { using Type = android::FillExtrusionLayer; };
template <> struct PeerType<style::FillLayer> { using Type = android::FillLayer; };
+template <> struct PeerType<style::HeatmapLayer> { using Type = android::HeatmapLayer; };
template <> struct PeerType<style::HillshadeLayer> { using Type = android::HillshadeLayer; };
template <> struct PeerType<style::LineLayer> { using Type = android::LineLayer; };
template <> struct PeerType<style::RasterLayer> { using Type = android::RasterLayer; };
@@ -95,6 +98,7 @@ void registerNativeLayers(jni::JNIEnv& env) {
CustomLayer::registerNative(env);
FillExtrusionLayer::registerNative(env);
FillLayer::registerNative(env);
+ HeatmapLayer::registerNative(env);
HillshadeLayer::registerNative(env);
LineLayer::registerNative(env);
RasterLayer::registerNative(env);
diff --git a/platform/android/src/style/sources/custom_geometry_source.cpp b/platform/android/src/style/sources/custom_geometry_source.cpp
index a60b962e45..b38405a3b1 100644
--- a/platform/android/src/style/sources/custom_geometry_source.cpp
+++ b/platform/android/src/style/sources/custom_geometry_source.cpp
@@ -18,7 +18,7 @@ namespace mbgl {
namespace android {
// This conversion is expected not to fail because it's used only in contexts where
- // the value was originally a GeoJsonOptions object on the Java side. If it fails
+ // the value was originally a CustomGeometrySourceOptions object on the Java side. If it fails
// to convert, it's a bug in our serialization or Java-side static typing.
static style::CustomGeometrySource::Options convertCustomGeometrySourceOptions(jni::JNIEnv& env,
jni::Object<> options,
diff --git a/platform/android/src/style/sources/image_source.cpp b/platform/android/src/style/sources/image_source.cpp
index 0cd6995969..249387ea51 100644
--- a/platform/android/src/style/sources/image_source.cpp
+++ b/platform/android/src/style/sources/image_source.cpp
@@ -45,6 +45,11 @@ namespace android {
source.as<mbgl::style::ImageSource>()->setImage(Bitmap::GetImage(env, bitmap));
}
+ void ImageSource::setCoordinates(jni::JNIEnv& env, jni::Object<LatLngQuad> coordinatesObject) {
+ source.as<mbgl::style::ImageSource>()->setCoordinates(
+ LatLngQuad::getLatLngArray(env, coordinatesObject));
+ }
+
jni::Class<ImageSource> ImageSource::javaClass;
jni::Object<Source> ImageSource::createJavaPeer(jni::JNIEnv& env) {
@@ -66,7 +71,8 @@ namespace android {
"finalize",
METHOD(&ImageSource::setURL, "nativeSetUrl"),
METHOD(&ImageSource::getURL, "nativeGetUrl"),
- METHOD(&ImageSource::setImage, "nativeSetImage")
+ METHOD(&ImageSource::setImage, "nativeSetImage"),
+ METHOD(&ImageSource::setCoordinates, "nativeSetCoordinates")
);
}
diff --git a/platform/android/src/style/sources/image_source.hpp b/platform/android/src/style/sources/image_source.hpp
index f0af28d357..6021a03dc3 100644
--- a/platform/android/src/style/sources/image_source.hpp
+++ b/platform/android/src/style/sources/image_source.hpp
@@ -30,6 +30,8 @@ public:
void setImage(jni::JNIEnv&, jni::Object<Bitmap>);
+ void setCoordinates(jni::JNIEnv&, jni::Object<LatLngQuad>);
+
private:
jni::Object<Source> createJavaPeer(jni::JNIEnv&);
diff --git a/platform/darwin/docs/guides/For Style Authors.md.ejs b/platform/darwin/docs/guides/For Style Authors.md.ejs
index 3f6c3fc2a4..7648d6f7ac 100644
--- a/platform/darwin/docs/guides/For Style Authors.md.ejs
+++ b/platform/darwin/docs/guides/For Style Authors.md.ejs
@@ -160,6 +160,7 @@ the following terms for concepts defined in the style specification:
In the style specification | In the SDK
---------------------------|---------
+bounds | coordinate bounds
filter | predicate
function type | interpolation mode
id | identifier
@@ -200,9 +201,11 @@ In style JSON | In TileJSON | In the SDK
`tiles` | `tiles` | `tileURLTemplates` parameter in `-[MGLTileSource initWithIdentifier:tileURLTemplates:options:]`
`minzoom` | `minzoom` | `MGLTileSourceOptionMinimumZoomLevel`
`maxzoom` | `maxzoom` | `MGLTileSourceOptionMaximumZoomLevel`
+`bounds` | `bounds` | `MGLTileSourceOptionCoordinateBounds`
`tileSize` | — | `MGLTileSourceOptionTileSize`
`attribution` | `attribution` | `MGLTileSourceOptionAttributionHTMLString` (but consider specifying `MGLTileSourceOptionAttributionInfos` instead for improved security)
`scheme` | `scheme` | `MGLTileSourceOptionTileCoordinateSystem`
+`encoding` | – | `MGLTileSourceOptionDEMEncoding`
### Shape sources
diff --git a/platform/darwin/scripts/generate-style-code.js b/platform/darwin/scripts/generate-style-code.js
index a7804ac948..53a668d10b 100755
--- a/platform/darwin/scripts/generate-style-code.js
+++ b/platform/darwin/scripts/generate-style-code.js
@@ -308,14 +308,15 @@ global.propertyDoc = function (propertyName, property, layerType, kind) {
doc += '* Predefined functions, including mathematical and string operators\n' +
'* Conditional expressions\n' +
'* Variable assignments and references to assigned variables\n';
+ const inputVariable = property.name === 'heatmap-color' ? '$heatmapDensity' : '$zoomLevel';
if (property["property-function"]) {
- doc += '* Interpolation and step functions applied to the `$zoomLevel` variable and/or feature attributes\n';
+ doc += `* Interpolation and step functions applied to the \`${inputVariable}\` variable and/or feature attributes\n`;
} else if (property.function === "interpolated") {
- doc += '* Interpolation and step functions applied to the `$zoomLevel` variable\n\n' +
+ doc += `* Interpolation and step functions applied to the \`${inputVariable}\` variable\n\n` +
'This property does not support applying interpolation or step functions to feature attributes.';
} else {
- doc += '* Step functions applied to the `$zoomLevel` variable\n\n' +
- 'This property does not support applying interpolation functions to the `$zoomLevel` variable or applying interpolation or step functions to feature attributes.';
+ doc += `* Step functions applied to the \`${inputVariable}\` variable\n\n` +
+ `This property does not support applying interpolation functions to the \`${inputVariable}\` variable or applying interpolation or step functions to feature attributes.`;
}
}
return doc;
@@ -387,7 +388,7 @@ global.describeValue = function (value, property, layerType) {
throw new Error(`No description available for ${value[0]} expression in ${property.name} of ${layerType}.`);
}
}
-
+
switch (property.type) {
case 'boolean':
return value ? '`YES`' : '`NO`';
diff --git a/platform/darwin/scripts/style-spec-overrides-v8.json b/platform/darwin/scripts/style-spec-overrides-v8.json
index 12cfa31575..b0c50a06f8 100644
--- a/platform/darwin/scripts/style-spec-overrides-v8.json
+++ b/platform/darwin/scripts/style-spec-overrides-v8.json
@@ -23,6 +23,9 @@
"circle": {
"doc": "An `MGLCircleStyleLayer` is a style layer that renders one or more filled circles on the map.\n\nUse a circle style layer to configure the visual appearance of point or point collection features in vector tiles loaded by an `MGLVectorSource` object or `MGLPointAnnotation`, `MGLPointFeature`, `MGLPointCollection`, or `MGLPointCollectionFeature` instances in an `MGLShapeSource` object.\n\nA circle style layer renders circles whose radii are measured in screen units. To display circles on the map whose radii correspond to real-world distances, use many-sided regular polygons and configure their appearance using an `MGLFillStyleLayer` object."
},
+ "heatmap": {
+ "doc": "An `MGLHeatmapStyleLayer` is a style layer that renders a <a href=\"https://en.wikipedia.org/wiki/Heat_map\">heatmap</a>.\n\nA heatmap visualizes the spatial distribution of a large, dense set of point data, using color to avoid cluttering the map with individual points at low zoom levels. The points are weighted by an attribute you specify. Use a heatmap style layer in conjunction with point or point collection features in vector tiles loaded by an `MGLVectorSource` object or `MGLPointAnnotation`, `MGLPointFeature`, `MGLPointCollection`, or `MGLPointCollectionFeature` instances in an `MGLShapeSource` object.\n\nConsider accompanying a heatmap style layer with an `MGLCircleStyleLayer` or `MGLSymbolStyleLayer` at high zoom levels. If you are unsure whether the point data in an `MGLShapeSource` is dense enough to warrant a heatmap, you can alternatively cluster the source using the `MGLShapeSourceOptionClustered` option and render the data using an `MGLCircleStyleLayer` or `MGLSymbolStyleLayer`."
+ },
"raster": {
"doc": "An `MGLRasterStyleLayer` is a style layer that renders georeferenced raster imagery on the map, especially raster tiles.\n\nUse a raster style layer to configure the color parameters of raster tiles loaded by an `MGLRasterSource` object or raster images loaded by an `MGLImageSource` object. For example, you could use a raster style layer to render <a href=\"https://www.mapbox.com/satellite/\">Mapbox Satellite</a> imagery, a <a href=\"https://www.mapbox.com/help/define-tileset/#raster-tilesets\">raster tile set</a> uploaded to Mapbox Studio, or a raster map authored in <a href=\"https://tilemill-project.github.io/tilemill/\">TileMill</a>, the classic Mapbox Editor, or Mapbox Studio Classic.\n\nRaster images may also be used as icons or patterns in a style layer. To register an image for use as an icon or pattern, use the `-[MGLStyle setImage:forName:]` method. To configure a point annotation’s image, use the `MGLAnnotationImage` class."
},
@@ -79,6 +82,11 @@
"doc": "The base color of this layer. The extrusion's surfaces will be shaded differently based on this color in combination with the `light` settings. If this color is specified with an alpha component, the alpha component will be ignored; use `fill-extrusion-opacity` to set layer opacityco."
}
},
+ "paint_heatmap": {
+ "heatmap-color": {
+ "doc": "Defines the color of each pixel based on its density value in a heatmap. Should be an expression that uses `$heatmapDensity` as input."
+ }
+ },
"paint_line": {
"line-pattern": {
"doc": "Name of image in style images to use for drawing image lines. For seamless patterns, image width must be a factor of two (2, 4, 8, ..., 512)."
@@ -114,4 +122,4 @@
"doc": "Distance that the text's anchor is moved from its original placement."
}
}
-} \ No newline at end of file
+}
diff --git a/platform/darwin/src/MGLAbstractShapeSource.h b/platform/darwin/src/MGLAbstractShapeSource.h
index 3b35986b3f..d61f41fbf6 100644
--- a/platform/darwin/src/MGLAbstractShapeSource.h
+++ b/platform/darwin/src/MGLAbstractShapeSource.h
@@ -82,6 +82,31 @@ extern MGL_EXPORT const MGLShapeSourceOption MGLShapeSourceOptionBuffer;
extern MGL_EXPORT const MGLShapeSourceOption MGLShapeSourceOptionSimplificationTolerance;
/**
+ An `NSNumber` object containing a Boolean value; specifies whether the shape of
+ an `MGLComputedShapeSource` should be wrapped to accomodate coordinates with
+ longitudes beyond −180 and 180. The default value is `NO`.
+
+ Setting this option to `YES` affects rendering performance.
+
+ This option is ignored when creating an instance of a class besides
+ `MGLComputedShapeSource`.
+ */
+extern MGL_EXPORT const MGLShapeSourceOption MGLShapeSourceOptionWrapsCoordinates;
+
+/**
+ An `NSNumber` object containing a Boolean value; specifies whether the shape of
+ an `MGLComputedShapeSource` should be clipped at the edge of each tile. The
+ default value is `NO`.
+
+ Setting this option to `YES` affects rendering performance. Use this option to
+ clip `MGLPolyline`s and `MGLPolygon`s at tile boundaries without artifacts.
+
+ This option is ignored when creating an instance of a class besides
+ `MGLComputedShapeSource`.
+ */
+extern MGL_EXPORT const MGLShapeSourceOption MGLShapeSourceOptionClipsCoordinates;
+
+/**
`MGLAbstractShapeSource` is an abstract base class for map content sources that
supply vector shapes to be shown on the map. A shape source is added to an
`MGLStyle` object along with an `MGLVectorStyleLayer` object. The vector style
diff --git a/platform/darwin/src/MGLAbstractShapeSource.mm b/platform/darwin/src/MGLAbstractShapeSource.mm
index 755d040387..eb0807cee0 100644
--- a/platform/darwin/src/MGLAbstractShapeSource.mm
+++ b/platform/darwin/src/MGLAbstractShapeSource.mm
@@ -8,6 +8,8 @@ const MGLShapeSourceOption MGLShapeSourceOptionMaximumZoomLevel = @"MGLShapeSour
const MGLShapeSourceOption MGLShapeSourceOptionMaximumZoomLevelForClustering = @"MGLShapeSourceOptionMaximumZoomLevelForClustering";
const MGLShapeSourceOption MGLShapeSourceOptionMinimumZoomLevel = @"MGLShapeSourceOptionMinimumZoomLevel";
const MGLShapeSourceOption MGLShapeSourceOptionSimplificationTolerance = @"MGLShapeSourceOptionSimplificationTolerance";
+const MGLShapeSourceOption MGLShapeSourceOptionWrapsCoordinates = @"MGLShapeSourceOptionWrapsCoordinates";
+const MGLShapeSourceOption MGLShapeSourceOptionClipsCoordinates = @"MGLShapeSourceOptionClipsCoordinates";
@interface MGLAbstractShapeSource ()
@@ -113,5 +115,22 @@ mbgl::style::CustomGeometrySource::Options MBGLCustomGeometrySourceOptionsFromDi
}
sourceOptions.tileOptions.tolerance = value.doubleValue;
}
+
+ if (NSNumber *value = options[MGLShapeSourceOptionWrapsCoordinates]) {
+ if (![value isKindOfClass:[NSNumber class]]) {
+ [NSException raise:NSInvalidArgumentException
+ format:@"MGLShapeSourceOptionWrapsCoordinates must be an NSNumber."];
+ }
+ sourceOptions.tileOptions.wrap = value.boolValue;
+ }
+
+ if (NSNumber *value = options[MGLShapeSourceOptionClipsCoordinates]) {
+ if (![value isKindOfClass:[NSNumber class]]) {
+ [NSException raise:NSInvalidArgumentException
+ format:@"MGLShapeSourceOptionClipsCoordinates must be an NSNumber."];
+ }
+ sourceOptions.tileOptions.clip = value.boolValue;
+ }
+
return sourceOptions;
}
diff --git a/platform/darwin/src/MGLHeatmapStyleLayer.h b/platform/darwin/src/MGLHeatmapStyleLayer.h
new file mode 100644
index 0000000000..35095fd52e
--- /dev/null
+++ b/platform/darwin/src/MGLHeatmapStyleLayer.h
@@ -0,0 +1,189 @@
+// This file is generated.
+// Edit platform/darwin/scripts/generate-style-code.js, then run `make darwin-style-code`.
+
+#import "MGLFoundation.h"
+#import "MGLVectorStyleLayer.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ An `MGLHeatmapStyleLayer` is a style layer that renders a <a
+ href="https://en.wikipedia.org/wiki/Heat_map">heatmap</a>.
+
+ A heatmap visualizes the spatial distribution of a large, dense set of point
+ data, using color to avoid cluttering the map with individual points at low
+ zoom levels. The points are weighted by an attribute you specify. Use a heatmap
+ style layer in conjunction with point or point collection features in vector
+ tiles loaded by an `MGLVectorSource` object or `MGLPointAnnotation`,
+ `MGLPointFeature`, `MGLPointCollection`, or `MGLPointCollectionFeature`
+ instances in an `MGLShapeSource` object.
+
+ Consider accompanying a heatmap style layer with an `MGLCircleStyleLayer` or
+ `MGLSymbolStyleLayer` at high zoom levels. If you are unsure whether the point
+ data in an `MGLShapeSource` is dense enough to warrant a heatmap, you can
+ alternatively cluster the source using the `MGLShapeSourceOptionClustered`
+ option and render the data using an `MGLCircleStyleLayer` or
+ `MGLSymbolStyleLayer`.
+
+ You can access an existing heatmap style layer using the
+ `-[MGLStyle layerWithIdentifier:]` method if you know its identifier;
+ otherwise, find it using the `MGLStyle.layers` property. You can also create a
+ new heatmap style layer and add it to the style using a method such as
+ `-[MGLStyle addLayer:]`.
+
+ ### Example
+
+ ```swift
+ let layer = MGLHeatmapStyleLayer(identifier: "earthquake-heat", source: earthquakes)
+ layer.heatmapWeight = NSExpression(format: "FUNCTION(magnitude, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)",
+ [0: 0,
+ 6: 1])
+ layer.heatmapIntensity = NSExpression(format: "FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)",
+ [0: 1,
+ 9: 3])
+ mapView.style?.addLayer(layer)
+ ```
+ */
+MGL_EXPORT
+@interface MGLHeatmapStyleLayer : MGLVectorStyleLayer
+
+/**
+ Returns a heatmap style layer initialized with an identifier and source.
+
+ After initializing and configuring the style layer, add it to a map view’s
+ style using the `-[MGLStyle addLayer:]` or
+ `-[MGLStyle insertLayer:belowLayer:]` method.
+
+ @param identifier A string that uniquely identifies the source in the style to
+ which it is added.
+ @param source The source from which to obtain the data to style. If the source
+ has not yet been added to the current style, the behavior is undefined.
+ @return An initialized foreground style layer.
+ */
+- (instancetype)initWithIdentifier:(NSString *)identifier source:(MGLSource *)source;
+
+#pragma mark - Accessing the Paint Attributes
+
+/**
+ Defines the color of each point based on its density value in a heatmap. Should
+ be an expression that uses `$heatmapDensity` as input.
+
+ The default value of this property is an expression that evaluates to a rainbow
+ color scale from blue to red. Set this property to `nil` to reset it to the
+ default value.
+
+ You can set this property to an expression containing any of the following:
+
+ * Constant `UIColor` values
+ * Predefined functions, including mathematical and string operators
+ * Conditional expressions
+ * Variable assignments and references to assigned variables
+ * Interpolation and step functions applied to the `$heatmapDensity` variable
+
+ This property does not support applying interpolation or step functions to
+ feature attributes.
+ */
+@property (nonatomic, null_resettable) NSExpression *heatmapColor;
+
+/**
+ Similar to `heatmapWeight` but controls the intensity of the heatmap globally.
+ Primarily used for adjusting the heatmap based on zoom level.
+
+ The default value of this property is an expression that evaluates to the float
+ `1`. Set this property to `nil` to reset it to the default value.
+
+ You can set this property to an expression containing any of the following:
+
+ * Constant numeric values
+ * Predefined functions, including mathematical and string operators
+ * Conditional expressions
+ * Variable assignments and references to assigned variables
+ * Interpolation and step functions applied to the `$zoomLevel` variable
+
+ This property does not support applying interpolation or step functions to
+ feature attributes.
+ */
+@property (nonatomic, null_resettable) NSExpression *heatmapIntensity;
+
+/**
+ The transition affecting any changes to this layer’s `heatmapIntensity` property.
+
+ This property corresponds to the `heatmap-intensity-transition` property in the style JSON file format.
+*/
+@property (nonatomic) MGLTransition heatmapIntensityTransition;
+
+/**
+ The global opacity at which the heatmap layer will be drawn.
+
+ The default value of this property is an expression that evaluates to the float
+ `1`. Set this property to `nil` to reset it to the default value.
+
+ You can set this property to an expression containing any of the following:
+
+ * Constant numeric values
+ * Predefined functions, including mathematical and string operators
+ * Conditional expressions
+ * Variable assignments and references to assigned variables
+ * Interpolation and step functions applied to the `$zoomLevel` variable
+
+ This property does not support applying interpolation or step functions to
+ feature attributes.
+ */
+@property (nonatomic, null_resettable) NSExpression *heatmapOpacity;
+
+/**
+ The transition affecting any changes to this layer’s `heatmapOpacity` property.
+
+ This property corresponds to the `heatmap-opacity-transition` property in the style JSON file format.
+*/
+@property (nonatomic) MGLTransition heatmapOpacityTransition;
+
+/**
+ Radius of influence of one heatmap point in points. Increasing the value makes
+ the heatmap smoother, but less detailed.
+
+ This property is measured in points.
+
+ The default value of this property is an expression that evaluates to the float
+ `30`. Set this property to `nil` to reset it to the default value.
+
+ You can set this property to an expression containing any of the following:
+
+ * Constant numeric values
+ * Predefined functions, including mathematical and string operators
+ * Conditional expressions
+ * Variable assignments and references to assigned variables
+ * Interpolation and step functions applied to the `$zoomLevel` variable and/or
+ feature attributes
+ */
+@property (nonatomic, null_resettable) NSExpression *heatmapRadius;
+
+/**
+ The transition affecting any changes to this layer’s `heatmapRadius` property.
+
+ This property corresponds to the `heatmap-radius-transition` property in the style JSON file format.
+*/
+@property (nonatomic) MGLTransition heatmapRadiusTransition;
+
+/**
+ A measure of how much an individual point contributes to the heatmap. A value
+ of 10 would be equivalent to having 10 points of weight 1 in the same spot.
+ Especially useful when combined with clustering.
+
+ The default value of this property is an expression that evaluates to the float
+ `1`. Set this property to `nil` to reset it to the default value.
+
+ You can set this property to an expression containing any of the following:
+
+ * Constant numeric values
+ * Predefined functions, including mathematical and string operators
+ * Conditional expressions
+ * Variable assignments and references to assigned variables
+ * Interpolation and step functions applied to the `$zoomLevel` variable and/or
+ feature attributes
+ */
+@property (nonatomic, null_resettable) NSExpression *heatmapWeight;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/platform/darwin/src/MGLHeatmapStyleLayer.mm b/platform/darwin/src/MGLHeatmapStyleLayer.mm
new file mode 100644
index 0000000000..a394dbda3b
--- /dev/null
+++ b/platform/darwin/src/MGLHeatmapStyleLayer.mm
@@ -0,0 +1,210 @@
+// This file is generated.
+// Edit platform/darwin/scripts/generate-style-code.js, then run `make darwin-style-code`.
+
+#import "MGLSource.h"
+#import "NSPredicate+MGLAdditions.h"
+#import "NSDate+MGLAdditions.h"
+#import "MGLStyleLayer_Private.h"
+#import "MGLStyleValue_Private.h"
+#import "MGLHeatmapStyleLayer.h"
+
+#include <mbgl/style/transition_options.hpp>
+#include <mbgl/style/layers/heatmap_layer.hpp>
+
+@interface MGLHeatmapStyleLayer ()
+
+@property (nonatomic, readonly) mbgl::style::HeatmapLayer *rawLayer;
+
+@end
+
+@implementation MGLHeatmapStyleLayer
+
+- (instancetype)initWithIdentifier:(NSString *)identifier source:(MGLSource *)source
+{
+ auto layer = std::make_unique<mbgl::style::HeatmapLayer>(identifier.UTF8String, source.identifier.UTF8String);
+ return self = [super initWithPendingLayer:std::move(layer)];
+}
+
+- (mbgl::style::HeatmapLayer *)rawLayer
+{
+ return (mbgl::style::HeatmapLayer *)super.rawLayer;
+}
+
+- (NSString *)sourceIdentifier
+{
+ MGLAssertStyleLayerIsValid();
+
+ return @(self.rawLayer->getSourceID().c_str());
+}
+
+- (NSString *)sourceLayerIdentifier
+{
+ MGLAssertStyleLayerIsValid();
+
+ auto layerID = self.rawLayer->getSourceLayer();
+ return layerID.empty() ? nil : @(layerID.c_str());
+}
+
+- (void)setSourceLayerIdentifier:(NSString *)sourceLayerIdentifier
+{
+ MGLAssertStyleLayerIsValid();
+
+ self.rawLayer->setSourceLayer(sourceLayerIdentifier.UTF8String ?: "");
+}
+
+- (void)setPredicate:(NSPredicate *)predicate
+{
+ MGLAssertStyleLayerIsValid();
+
+ self.rawLayer->setFilter(predicate ? predicate.mgl_filter : mbgl::style::NullFilter());
+}
+
+- (NSPredicate *)predicate
+{
+ MGLAssertStyleLayerIsValid();
+
+ return [NSPredicate mgl_predicateWithFilter:self.rawLayer->getFilter()];
+}
+
+#pragma mark - Accessing the Paint Attributes
+
+- (void)setHeatmapColor:(NSExpression *)heatmapColor {
+ MGLAssertStyleLayerIsValid();
+
+ auto mbglValue = MGLStyleValueTransformer<mbgl::Color, MGLColor *>().toPropertyValue<mbgl::style::HeatmapColorPropertyValue>(heatmapColor);
+ self.rawLayer->setHeatmapColor(mbglValue);
+}
+
+- (NSExpression *)heatmapColor {
+ MGLAssertStyleLayerIsValid();
+
+ auto propertyValue = self.rawLayer->getHeatmapColor();
+ if (propertyValue.isUndefined()) {
+ propertyValue = self.rawLayer->getDefaultHeatmapColor();
+ }
+ return MGLStyleValueTransformer<mbgl::Color, MGLColor *>().toExpression(propertyValue);
+}
+
+- (void)setHeatmapIntensity:(NSExpression *)heatmapIntensity {
+ MGLAssertStyleLayerIsValid();
+
+ auto mbglValue = MGLStyleValueTransformer<float, NSNumber *>().toPropertyValue<mbgl::style::PropertyValue<float>>(heatmapIntensity);
+ self.rawLayer->setHeatmapIntensity(mbglValue);
+}
+
+- (NSExpression *)heatmapIntensity {
+ MGLAssertStyleLayerIsValid();
+
+ auto propertyValue = self.rawLayer->getHeatmapIntensity();
+ if (propertyValue.isUndefined()) {
+ propertyValue = self.rawLayer->getDefaultHeatmapIntensity();
+ }
+ return MGLStyleValueTransformer<float, NSNumber *>().toExpression(propertyValue);
+}
+
+- (void)setHeatmapIntensityTransition:(MGLTransition )transition {
+ MGLAssertStyleLayerIsValid();
+
+ mbgl::style::TransitionOptions options { { MGLDurationFromTimeInterval(transition.duration) }, { MGLDurationFromTimeInterval(transition.delay) } };
+ self.rawLayer->setHeatmapIntensityTransition(options);
+}
+
+- (MGLTransition)heatmapIntensityTransition {
+ MGLAssertStyleLayerIsValid();
+
+ mbgl::style::TransitionOptions transitionOptions = self.rawLayer->getHeatmapIntensityTransition();
+ MGLTransition transition;
+ transition.duration = MGLTimeIntervalFromDuration(transitionOptions.duration.value_or(mbgl::Duration::zero()));
+ transition.delay = MGLTimeIntervalFromDuration(transitionOptions.delay.value_or(mbgl::Duration::zero()));
+
+ return transition;
+}
+
+- (void)setHeatmapOpacity:(NSExpression *)heatmapOpacity {
+ MGLAssertStyleLayerIsValid();
+
+ auto mbglValue = MGLStyleValueTransformer<float, NSNumber *>().toPropertyValue<mbgl::style::PropertyValue<float>>(heatmapOpacity);
+ self.rawLayer->setHeatmapOpacity(mbglValue);
+}
+
+- (NSExpression *)heatmapOpacity {
+ MGLAssertStyleLayerIsValid();
+
+ auto propertyValue = self.rawLayer->getHeatmapOpacity();
+ if (propertyValue.isUndefined()) {
+ propertyValue = self.rawLayer->getDefaultHeatmapOpacity();
+ }
+ return MGLStyleValueTransformer<float, NSNumber *>().toExpression(propertyValue);
+}
+
+- (void)setHeatmapOpacityTransition:(MGLTransition )transition {
+ MGLAssertStyleLayerIsValid();
+
+ mbgl::style::TransitionOptions options { { MGLDurationFromTimeInterval(transition.duration) }, { MGLDurationFromTimeInterval(transition.delay) } };
+ self.rawLayer->setHeatmapOpacityTransition(options);
+}
+
+- (MGLTransition)heatmapOpacityTransition {
+ MGLAssertStyleLayerIsValid();
+
+ mbgl::style::TransitionOptions transitionOptions = self.rawLayer->getHeatmapOpacityTransition();
+ MGLTransition transition;
+ transition.duration = MGLTimeIntervalFromDuration(transitionOptions.duration.value_or(mbgl::Duration::zero()));
+ transition.delay = MGLTimeIntervalFromDuration(transitionOptions.delay.value_or(mbgl::Duration::zero()));
+
+ return transition;
+}
+
+- (void)setHeatmapRadius:(NSExpression *)heatmapRadius {
+ MGLAssertStyleLayerIsValid();
+
+ auto mbglValue = MGLStyleValueTransformer<float, NSNumber *>().toPropertyValue<mbgl::style::DataDrivenPropertyValue<float>>(heatmapRadius);
+ self.rawLayer->setHeatmapRadius(mbglValue);
+}
+
+- (NSExpression *)heatmapRadius {
+ MGLAssertStyleLayerIsValid();
+
+ auto propertyValue = self.rawLayer->getHeatmapRadius();
+ if (propertyValue.isUndefined()) {
+ propertyValue = self.rawLayer->getDefaultHeatmapRadius();
+ }
+ return MGLStyleValueTransformer<float, NSNumber *>().toExpression(propertyValue);
+}
+
+- (void)setHeatmapRadiusTransition:(MGLTransition )transition {
+ MGLAssertStyleLayerIsValid();
+
+ mbgl::style::TransitionOptions options { { MGLDurationFromTimeInterval(transition.duration) }, { MGLDurationFromTimeInterval(transition.delay) } };
+ self.rawLayer->setHeatmapRadiusTransition(options);
+}
+
+- (MGLTransition)heatmapRadiusTransition {
+ MGLAssertStyleLayerIsValid();
+
+ mbgl::style::TransitionOptions transitionOptions = self.rawLayer->getHeatmapRadiusTransition();
+ MGLTransition transition;
+ transition.duration = MGLTimeIntervalFromDuration(transitionOptions.duration.value_or(mbgl::Duration::zero()));
+ transition.delay = MGLTimeIntervalFromDuration(transitionOptions.delay.value_or(mbgl::Duration::zero()));
+
+ return transition;
+}
+
+- (void)setHeatmapWeight:(NSExpression *)heatmapWeight {
+ MGLAssertStyleLayerIsValid();
+
+ auto mbglValue = MGLStyleValueTransformer<float, NSNumber *>().toPropertyValue<mbgl::style::DataDrivenPropertyValue<float>>(heatmapWeight);
+ self.rawLayer->setHeatmapWeight(mbglValue);
+}
+
+- (NSExpression *)heatmapWeight {
+ MGLAssertStyleLayerIsValid();
+
+ auto propertyValue = self.rawLayer->getHeatmapWeight();
+ if (propertyValue.isUndefined()) {
+ propertyValue = self.rawLayer->getDefaultHeatmapWeight();
+ }
+ return MGLStyleValueTransformer<float, NSNumber *>().toExpression(propertyValue);
+}
+
+@end
diff --git a/platform/darwin/src/MGLMapSnapshotter.mm b/platform/darwin/src/MGLMapSnapshotter.mm
index f03bd70c86..11a5442761 100644
--- a/platform/darwin/src/MGLMapSnapshotter.mm
+++ b/platform/darwin/src/MGLMapSnapshotter.mm
@@ -81,7 +81,7 @@ const CGFloat MGLSnapshotterMinimumPixelSize = 64;
@end
@interface MGLMapSnapshotter()
-
+@property (nonatomic) BOOL loading;
@end
@implementation MGLMapSnapshotter {
@@ -90,6 +90,7 @@ const CGFloat MGLSnapshotterMinimumPixelSize = 64;
std::unique_ptr<mbgl::MapSnapshotter> _mbglMapSnapshotter;
std::unique_ptr<mbgl::Actor<mbgl::MapSnapshotter::Callback>> _snapshotCallback;
NS_ARRAY_OF(MGLAttributionInfo *) *_attributionInfo;
+
}
- (instancetype)initWithOptions:(MGLMapSnapshotOptions *)options
@@ -115,182 +116,192 @@ const CGFloat MGLSnapshotterMinimumPixelSize = 64;
format:@"Already started this snapshotter."];
}
- _loading = true;
+ self.loading = true;
- dispatch_async(queue, ^{
- _snapshotCallback = std::make_unique<mbgl::Actor<mbgl::MapSnapshotter::Callback>>(*mbgl::Scheduler::GetCurrent(), [=](std::exception_ptr mbglError, mbgl::PremultipliedImage image, mbgl::MapSnapshotter::Attributions attributions, mbgl::MapSnapshotter::PointForFn pointForFn) {
- _loading = false;
+ __weak __typeof__(self) weakSelf = self;
+ _snapshotCallback = std::make_unique<mbgl::Actor<mbgl::MapSnapshotter::Callback>>(*mbgl::Scheduler::GetCurrent(), [=](std::exception_ptr mbglError, mbgl::PremultipliedImage image, mbgl::MapSnapshotter::Attributions attributions, mbgl::MapSnapshotter::PointForFn pointForFn) {
+ __typeof__(self) strongSelf = weakSelf;
+ strongSelf.loading = false;
+
+
+ if (mbglError) {
+ NSString *description = @(mbgl::util::toString(mbglError).c_str());
+ NSDictionary *userInfo = @{NSLocalizedDescriptionKey: description};
+ NSError *error = [NSError errorWithDomain:MGLErrorDomain code:MGLErrorCodeSnapshotFailed userInfo:userInfo];
- NSMutableArray *infos = [NSMutableArray array];
-
+ // Dispatch result to origin queue
+ dispatch_async(queue, ^{
+ completion(nil, error);
+ });
+ } else {
#if TARGET_OS_IPHONE
- CGFloat fontSize = [UIFont smallSystemFontSize];
- UIColor *attributeFontColor = [UIColor blackColor];
+ MGLImage *mglImage = [[MGLImage alloc] initWithMGLPremultipliedImage:std::move(image) scale:strongSelf.options.scale];
#else
- CGFloat fontSize = [NSFont systemFontSizeForControlSize:NSMiniControlSize];
- NSColor *attributeFontColor = [NSColor blackColor];
+ MGLImage *mglImage = [[MGLImage alloc] initWithMGLPremultipliedImage:std::move(image)];
+ mglImage.size = NSMakeSize(mglImage.size.width / strongSelf.options.scale,
+ mglImage.size.height / strongSelf.options.scale);
#endif
- for (auto attribution = attributions.begin(); attribution != attributions.end(); ++attribution) {
- NSString *attributionHTMLString = @(attribution->c_str());
- NSArray *tileSetInfos = [MGLAttributionInfo attributionInfosFromHTMLString:attributionHTMLString
- fontSize:fontSize
- linkColor:attributeFontColor];
- [infos growArrayByAddingAttributionInfosFromArray:tileSetInfos];
- }
-
- _attributionInfo = infos;
-
- if (mbglError) {
- NSString *description = @(mbgl::util::toString(mbglError).c_str());
- NSDictionary *userInfo = @{NSLocalizedDescriptionKey: description};
- NSError *error = [NSError errorWithDomain:MGLErrorDomain code:MGLErrorCodeSnapshotFailed userInfo:userInfo];
-
- // Dispatch result to origin queue
- dispatch_async(queue, ^{
- completion(nil, error);
- });
- } else {
+ [strongSelf drawAttributedSnapshot:attributions snapshotImage:mglImage pointForFn:pointForFn queue:queue completionHandler:completion];
+ }
+ _snapshotCallback = NULL;
+ });
+ dispatch_async(queue, ^{
+ _mbglMapSnapshotter->snapshot(_snapshotCallback->self());
+
+ });
+}
+
+- (MGLImage *)drawAttributedSnapshot:(mbgl::MapSnapshotter::Attributions)attributions snapshotImage:(MGLImage *)mglImage pointForFn:(mbgl::MapSnapshotter::PointForFn)pointForFn queue:(dispatch_queue_t)queue completionHandler:(MGLMapSnapshotCompletionHandler)completion {
+
+ NSMutableArray *infos = [NSMutableArray array];
+
#if TARGET_OS_IPHONE
- MGLImage *mglImage = [[MGLImage alloc] initWithMGLPremultipliedImage:std::move(image) scale:self.options.scale];
+ CGFloat fontSize = [UIFont smallSystemFontSize];
+ UIColor *attributeFontColor = [UIColor blackColor];
#else
- MGLImage *mglImage = [[MGLImage alloc] initWithMGLPremultipliedImage:std::move(image)];
- mglImage.size = NSMakeSize(mglImage.size.width / self.options.scale,
- mglImage.size.height / self.options.scale);
+ CGFloat fontSize = [NSFont systemFontSizeForControlSize:NSMiniControlSize];
+ NSColor *attributeFontColor = [NSColor blackColor];
#endif
-
- // Process image watermark in a work queue
- dispatch_queue_t workQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
- dispatch_async(workQueue, ^{
+ for (auto attribution = attributions.begin(); attribution != attributions.end(); ++attribution) {
+ NSString *attributionHTMLString = @(attribution->c_str());
+ NSArray *tileSetInfos = [MGLAttributionInfo attributionInfosFromHTMLString:attributionHTMLString
+ fontSize:fontSize
+ linkColor:attributeFontColor];
+ [infos growArrayByAddingAttributionInfosFromArray:tileSetInfos];
+ }
+
+ _attributionInfo = infos;
+
+ // Process image watermark in a work queue
+ dispatch_queue_t workQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
+ dispatch_async(workQueue, ^{
#if TARGET_OS_IPHONE
- MGLAttributionInfoStyle attributionInfoStyle = MGLAttributionInfoStyleLong;
- for (NSUInteger styleValue = MGLAttributionInfoStyleLong; styleValue >= MGLAttributionInfoStyleShort; styleValue--) {
- attributionInfoStyle = (MGLAttributionInfoStyle)styleValue;
- CGSize attributionSize = [self attributionSizeWithLogoStyle:attributionInfoStyle sourceAttributionStyle:attributionInfoStyle];
- if (attributionSize.width <= mglImage.size.width) {
- break;
- }
- }
-
- UIImage *logoImage = [self logoImageWithStyle:attributionInfoStyle];
- CGSize attributionBackgroundSize = [self attributionTextSizeWithStyle:attributionInfoStyle];
-
- CGRect logoImageRect = CGRectMake(MGLLogoImagePosition.x, mglImage.size.height - (MGLLogoImagePosition.y + logoImage.size.height), logoImage.size.width, logoImage.size.height);
- CGPoint attributionOrigin = CGPointMake(mglImage.size.width - 10 - attributionBackgroundSize.width,
- logoImageRect.origin.y + (logoImageRect.size.height / 2) - (attributionBackgroundSize.height / 2) + 1);
- if (!logoImage) {
- CGSize defaultLogoSize = [self mapboxLongStyleLogo].size;
- logoImageRect = CGRectMake(0, mglImage.size.height - (MGLLogoImagePosition.y + defaultLogoSize.height), 0, defaultLogoSize.height);
- attributionOrigin = CGPointMake(10, logoImageRect.origin.y + (logoImageRect.size.height / 2) - (attributionBackgroundSize.height / 2) + 1);
- }
-
- CGRect attributionBackgroundFrame = CGRectMake(attributionOrigin.x,
- attributionOrigin.y,
- attributionBackgroundSize.width,
- attributionBackgroundSize.height);
- CGPoint attributionTextPosition = CGPointMake(attributionBackgroundFrame.origin.x + 10,
- attributionBackgroundFrame.origin.y - 1);
-
- CGRect cropRect = CGRectMake(attributionBackgroundFrame.origin.x * mglImage.scale,
- attributionBackgroundFrame.origin.y * mglImage.scale,
- attributionBackgroundSize.width * mglImage.scale,
- attributionBackgroundSize.height * mglImage.scale);
-
-
- UIGraphicsBeginImageContextWithOptions(mglImage.size, NO, self.options.scale);
-
- [mglImage drawInRect:CGRectMake(0, 0, mglImage.size.width, mglImage.size.height)];
-
- [logoImage drawInRect:logoImageRect];
-
- UIImage *currentImage = UIGraphicsGetImageFromCurrentImageContext();
- CGImageRef attributionImageRef = CGImageCreateWithImageInRect([currentImage CGImage], cropRect);
- UIImage *attributionImage = [UIImage imageWithCGImage:attributionImageRef];
- CGImageRelease(attributionImageRef);
-
- CIImage *ciAttributionImage = [[CIImage alloc] initWithCGImage:attributionImage.CGImage];
-
- UIImage *blurredAttributionBackground = [self blurredAttributionBackground:ciAttributionImage];
-
- [blurredAttributionBackground drawInRect:attributionBackgroundFrame];
-
- [self drawAttributionTextWithStyle:attributionInfoStyle origin:attributionTextPosition];
-
- UIImage *compositedImage = UIGraphicsGetImageFromCurrentImageContext();
-
- UIGraphicsEndImageContext();
+ MGLAttributionInfoStyle attributionInfoStyle = MGLAttributionInfoStyleLong;
+ for (NSUInteger styleValue = MGLAttributionInfoStyleLong; styleValue >= MGLAttributionInfoStyleShort; styleValue--) {
+ attributionInfoStyle = (MGLAttributionInfoStyle)styleValue;
+ CGSize attributionSize = [self attributionSizeWithLogoStyle:attributionInfoStyle sourceAttributionStyle:attributionInfoStyle];
+ if (attributionSize.width <= mglImage.size.width) {
+ break;
+ }
+ }
+
+ UIImage *logoImage = [self logoImageWithStyle:attributionInfoStyle];
+ CGSize attributionBackgroundSize = [self attributionTextSizeWithStyle:attributionInfoStyle];
+
+ CGRect logoImageRect = CGRectMake(MGLLogoImagePosition.x, mglImage.size.height - (MGLLogoImagePosition.y + logoImage.size.height), logoImage.size.width, logoImage.size.height);
+ CGPoint attributionOrigin = CGPointMake(mglImage.size.width - 10 - attributionBackgroundSize.width,
+ logoImageRect.origin.y + (logoImageRect.size.height / 2) - (attributionBackgroundSize.height / 2) + 1);
+ if (!logoImage) {
+ CGSize defaultLogoSize = [self mapboxLongStyleLogo].size;
+ logoImageRect = CGRectMake(0, mglImage.size.height - (MGLLogoImagePosition.y + defaultLogoSize.height), 0, defaultLogoSize.height);
+ attributionOrigin = CGPointMake(10, logoImageRect.origin.y + (logoImageRect.size.height / 2) - (attributionBackgroundSize.height / 2) + 1);
+ }
+
+ CGRect attributionBackgroundFrame = CGRectMake(attributionOrigin.x,
+ attributionOrigin.y,
+ attributionBackgroundSize.width,
+ attributionBackgroundSize.height);
+ CGPoint attributionTextPosition = CGPointMake(attributionBackgroundFrame.origin.x + 10,
+ attributionBackgroundFrame.origin.y - 1);
+
+ CGRect cropRect = CGRectMake(attributionBackgroundFrame.origin.x * mglImage.scale,
+ attributionBackgroundFrame.origin.y * mglImage.scale,
+ attributionBackgroundSize.width * mglImage.scale,
+ attributionBackgroundSize.height * mglImage.scale);
+
+
+ UIGraphicsBeginImageContextWithOptions(mglImage.size, NO, self.options.scale);
+
+ [mglImage drawInRect:CGRectMake(0, 0, mglImage.size.width, mglImage.size.height)];
+
+ [logoImage drawInRect:logoImageRect];
+
+ UIImage *currentImage = UIGraphicsGetImageFromCurrentImageContext();
+ CGImageRef attributionImageRef = CGImageCreateWithImageInRect([currentImage CGImage], cropRect);
+ UIImage *attributionImage = [UIImage imageWithCGImage:attributionImageRef];
+ CGImageRelease(attributionImageRef);
+
+ CIImage *ciAttributionImage = [[CIImage alloc] initWithCGImage:attributionImage.CGImage];
+
+ UIImage *blurredAttributionBackground = [self blurredAttributionBackground:ciAttributionImage];
+
+ [blurredAttributionBackground drawInRect:attributionBackgroundFrame];
+
+ [self drawAttributionTextWithStyle:attributionInfoStyle origin:attributionTextPosition];
+
+ UIImage *compositedImage = UIGraphicsGetImageFromCurrentImageContext();
+
+ UIGraphicsEndImageContext();
#else
- NSSize targetSize = NSMakeSize(self.options.size.width, self.options.size.height);
- NSRect targetFrame = NSMakeRect(0, 0, targetSize.width, targetSize.height);
-
- MGLAttributionInfoStyle attributionInfoStyle = MGLAttributionInfoStyleLong;
- for (NSUInteger styleValue = MGLAttributionInfoStyleLong; styleValue >= MGLAttributionInfoStyleShort; styleValue--) {
- attributionInfoStyle = (MGLAttributionInfoStyle)styleValue;
- CGSize attributionSize = [self attributionSizeWithLogoStyle:attributionInfoStyle sourceAttributionStyle:attributionInfoStyle];
- if (attributionSize.width <= mglImage.size.width) {
- break;
- }
- }
-
- NSImage *logoImage = [self logoImageWithStyle:attributionInfoStyle];
- CGSize attributionBackgroundSize = [self attributionTextSizeWithStyle:attributionInfoStyle];
- NSImage *sourceImage = mglImage;
-
- CGRect logoImageRect = CGRectMake(MGLLogoImagePosition.x, MGLLogoImagePosition.y, logoImage.size.width, logoImage.size.height);
- CGPoint attributionOrigin = CGPointMake(targetFrame.size.width - 10 - attributionBackgroundSize.width,
- MGLLogoImagePosition.y + 1);
- if (!logoImage) {
- CGSize defaultLogoSize = [self mapboxLongStyleLogo].size;
- logoImageRect = CGRectMake(0, MGLLogoImagePosition.y, 0, defaultLogoSize.height);
- attributionOrigin = CGPointMake(10, attributionOrigin.y);
- }
-
- CGRect attributionBackgroundFrame = CGRectMake(attributionOrigin.x,
- attributionOrigin.y,
- attributionBackgroundSize.width,
- attributionBackgroundSize.height);
- CGPoint attributionTextPosition = CGPointMake(attributionBackgroundFrame.origin.x + 10,
- logoImageRect.origin.y + (logoImageRect.size.height / 2) - (attributionBackgroundSize.height / 2));
-
-
- NSImage *compositedImage = nil;
- NSImageRep *sourceImageRep = [sourceImage bestRepresentationForRect:targetFrame
- context:nil
- hints:nil];
- compositedImage = [[NSImage alloc] initWithSize:targetSize];
-
- [compositedImage lockFocus];
-
- [sourceImageRep drawInRect: targetFrame];
-
- if (logoImage) {
- [logoImage drawInRect:logoImageRect];
- }
-
- NSBitmapImageRep *attributionBackground = [[NSBitmapImageRep alloc] initWithFocusedViewRect:attributionBackgroundFrame];
-
- CIImage *attributionBackgroundImage = [[CIImage alloc] initWithCGImage:[attributionBackground CGImage]];
-
- NSImage *blurredAttributionBackground = [self blurredAttributionBackground:attributionBackgroundImage];
-
- [blurredAttributionBackground drawInRect:attributionBackgroundFrame];
-
- [self drawAttributionTextWithStyle:attributionInfoStyle origin:attributionTextPosition];
-
- [compositedImage unlockFocus];
-
-
-#endif
-
- // Dispatch result to origin queue
- dispatch_async(queue, ^{
- MGLMapSnapshot* snapshot = [[MGLMapSnapshot alloc] initWithImage:compositedImage scale:self.options.scale pointForFn:pointForFn];
- completion(snapshot, nil);
- });
- });
+ NSSize targetSize = NSMakeSize(self.options.size.width, self.options.size.height);
+ NSRect targetFrame = NSMakeRect(0, 0, targetSize.width, targetSize.height);
+
+ MGLAttributionInfoStyle attributionInfoStyle = MGLAttributionInfoStyleLong;
+ for (NSUInteger styleValue = MGLAttributionInfoStyleLong; styleValue >= MGLAttributionInfoStyleShort; styleValue--) {
+ attributionInfoStyle = (MGLAttributionInfoStyle)styleValue;
+ CGSize attributionSize = [self attributionSizeWithLogoStyle:attributionInfoStyle sourceAttributionStyle:attributionInfoStyle];
+ if (attributionSize.width <= mglImage.size.width) {
+ break;
}
+ }
+
+ NSImage *logoImage = [self logoImageWithStyle:attributionInfoStyle];
+ CGSize attributionBackgroundSize = [self attributionTextSizeWithStyle:attributionInfoStyle];
+ NSImage *sourceImage = mglImage;
+
+ CGRect logoImageRect = CGRectMake(MGLLogoImagePosition.x, MGLLogoImagePosition.y, logoImage.size.width, logoImage.size.height);
+ CGPoint attributionOrigin = CGPointMake(targetFrame.size.width - 10 - attributionBackgroundSize.width,
+ MGLLogoImagePosition.y + 1);
+ if (!logoImage) {
+ CGSize defaultLogoSize = [self mapboxLongStyleLogo].size;
+ logoImageRect = CGRectMake(0, MGLLogoImagePosition.y, 0, defaultLogoSize.height);
+ attributionOrigin = CGPointMake(10, attributionOrigin.y);
+ }
+
+ CGRect attributionBackgroundFrame = CGRectMake(attributionOrigin.x,
+ attributionOrigin.y,
+ attributionBackgroundSize.width,
+ attributionBackgroundSize.height);
+ CGPoint attributionTextPosition = CGPointMake(attributionBackgroundFrame.origin.x + 10,
+ logoImageRect.origin.y + (logoImageRect.size.height / 2) - (attributionBackgroundSize.height / 2));
+
+
+ NSImage *compositedImage = nil;
+ NSImageRep *sourceImageRep = [sourceImage bestRepresentationForRect:targetFrame
+ context:nil
+ hints:nil];
+ compositedImage = [[NSImage alloc] initWithSize:targetSize];
+
+ [compositedImage lockFocus];
+
+ [sourceImageRep drawInRect: targetFrame];
+
+ if (logoImage) {
+ [logoImage drawInRect:logoImageRect];
+ }
+
+ NSBitmapImageRep *attributionBackground = [[NSBitmapImageRep alloc] initWithFocusedViewRect:attributionBackgroundFrame];
+
+ CIImage *attributionBackgroundImage = [[CIImage alloc] initWithCGImage:[attributionBackground CGImage]];
+
+ NSImage *blurredAttributionBackground = [self blurredAttributionBackground:attributionBackgroundImage];
+
+ [blurredAttributionBackground drawInRect:attributionBackgroundFrame];
+
+ [self drawAttributionTextWithStyle:attributionInfoStyle origin:attributionTextPosition];
+
+ [compositedImage unlockFocus];
+
+
+#endif
+
+ // Dispatch result to origin queue
+ dispatch_async(queue, ^{
+ MGLMapSnapshot* snapshot = [[MGLMapSnapshot alloc] initWithImage:compositedImage scale:self.options.scale pointForFn:pointForFn];
+ completion(snapshot, nil);
});
- _mbglMapSnapshotter->snapshot(_snapshotCallback->self());
});
+ return nil;
}
- (void)drawAttributionTextWithStyle:(MGLAttributionInfoStyle)attributionInfoStyle origin:(CGPoint)origin
diff --git a/platform/darwin/src/MGLRendererFrontend.h b/platform/darwin/src/MGLRendererFrontend.h
index 76904d008b..2df67ca4e4 100644
--- a/platform/darwin/src/MGLRendererFrontend.h
+++ b/platform/darwin/src/MGLRendererFrontend.h
@@ -56,9 +56,9 @@ public:
return renderer.get();
}
- void onLowMemory() {
+ void reduceMemoryUse() {
if (!renderer) return;
- renderer->onLowMemory();
+ renderer->reduceMemoryUse();
}
private:
diff --git a/platform/darwin/src/MGLStyle.mm b/platform/darwin/src/MGLStyle.mm
index 5128944312..f6fc5533be 100644
--- a/platform/darwin/src/MGLStyle.mm
+++ b/platform/darwin/src/MGLStyle.mm
@@ -8,6 +8,7 @@
#import "MGLLineStyleLayer.h"
#import "MGLCircleStyleLayer.h"
#import "MGLSymbolStyleLayer.h"
+#import "MGLHeatmapStyleLayer.h"
#import "MGLHillshadeStyleLayer.h"
#import "MGLRasterStyleLayer.h"
#import "MGLBackgroundStyleLayer.h"
@@ -36,6 +37,7 @@
#include <mbgl/style/layers/line_layer.hpp>
#include <mbgl/style/layers/symbol_layer.hpp>
#include <mbgl/style/layers/raster_layer.hpp>
+#include <mbgl/style/layers/heatmap_layer.hpp>
#include <mbgl/style/layers/hillshade_layer.hpp>
#include <mbgl/style/layers/circle_layer.hpp>
#include <mbgl/style/layers/background_layer.hpp>
@@ -401,6 +403,8 @@ static NSURL *MGLStyleURL_trafficNight;
return [[MGLSymbolStyleLayer alloc] initWithRawLayer:symbolLayer];
} else if (auto rasterLayer = rawLayer->as<mbgl::style::RasterLayer>()) {
return [[MGLRasterStyleLayer alloc] initWithRawLayer:rasterLayer];
+ } else if (auto heatmapLayer = rawLayer->as<mbgl::style::HeatmapLayer>()) {
+ return [[MGLHeatmapStyleLayer alloc] initWithRawLayer:heatmapLayer];
} else if (auto hillshadeLayer = rawLayer->as<mbgl::style::HillshadeLayer>()) {
return [[MGLHillshadeStyleLayer alloc] initWithRawLayer:hillshadeLayer];
} else if (auto circleLayer = rawLayer->as<mbgl::style::CircleLayer>()) {
diff --git a/platform/darwin/src/MGLStyleLayer.mm.ejs b/platform/darwin/src/MGLStyleLayer.mm.ejs
index 41b029791f..ac7676a1cc 100644
--- a/platform/darwin/src/MGLStyleLayer.mm.ejs
+++ b/platform/darwin/src/MGLStyleLayer.mm.ejs
@@ -157,7 +157,9 @@ namespace mbgl {
- (void)set<%- camelize(property.name) %>:(NSExpression *)<%- objCName(property) %> {
MGLAssertStyleLayerIsValid();
-<% if (property["property-function"]) { -%>
+<% if (property.name === 'heatmap-color') { -%>
+ auto mbglValue = MGLStyleValueTransformer<mbgl::Color, MGLColor *>().toPropertyValue<mbgl::style::HeatmapColorPropertyValue>(heatmapColor);
+<% } else if (property["property-function"]) { -%>
auto mbglValue = MGLStyleValueTransformer<<%- valueTransformerArguments(property).join(', ') %>>().toPropertyValue<mbgl::style::DataDrivenPropertyValue<<%- valueTransformerArguments(property)[0] %>>>(<%- objCName(property) %>);
<% } else { -%>
auto mbglValue = MGLStyleValueTransformer<<%- valueTransformerArguments(property).join(', ') %>>().toPropertyValue<mbgl::style::PropertyValue<<%- valueTransformerArguments(property)[0] %>>>(<%- objCName(property) %>);
diff --git a/platform/darwin/src/MGLStyleValue_Private.h b/platform/darwin/src/MGLStyleValue_Private.h
index ba4b413a3c..5124c29a90 100644
--- a/platform/darwin/src/MGLStyleValue_Private.h
+++ b/platform/darwin/src/MGLStyleValue_Private.h
@@ -10,6 +10,7 @@
#import "MGLConversion.h"
#include <mbgl/style/conversion/property_value.hpp>
#include <mbgl/style/conversion/data_driven_property_value.hpp>
+#include <mbgl/style/conversion/heatmap_color_property_value.hpp>
#include <mbgl/style/conversion/position.hpp>
#import <mbgl/style/types.hpp>
@@ -52,11 +53,20 @@ public:
return mbglValue.evaluate(evaluator);
}
+ // Convert an mbgl heatmap color property value into an mgl style value
+ NSExpression *toExpression(const mbgl::style::HeatmapColorPropertyValue &mbglValue) {
+ if (mbglValue.isUndefined()) {
+ return nil;
+ }
+ return [NSExpression mgl_expressionWithJSONObject:MGLJSONObjectFromMBGLExpression(mbglValue.getExpression())];
+ }
+
/**
Converts an NSExpression to an mbgl property value.
*/
template <typename MBGLValue>
- MBGLValue toPropertyValue(NSExpression *expression) {
+ typename std::enable_if_t<!std::is_same<MBGLValue, mbgl::style::HeatmapColorPropertyValue>::value,
+ MBGLValue> toPropertyValue(NSExpression *expression) {
if (!expression) {
return {};
}
@@ -85,6 +95,30 @@ public:
return *value;
}
+
+ /**
+ Converts an NSExpression to an mbgl property value.
+ */
+ template <typename MBGLValue>
+ typename std::enable_if_t<std::is_same<MBGLValue, mbgl::style::HeatmapColorPropertyValue>::value,
+ MBGLValue> toPropertyValue(NSExpression *expression) {
+ if (!expression) {
+ return {};
+ }
+
+ NSArray *jsonExpression = expression.mgl_jsonExpressionObject;
+
+ mbgl::style::conversion::Error valueError;
+ auto value = mbgl::style::conversion::convert<mbgl::style::HeatmapColorPropertyValue>(
+ mbgl::style::conversion::makeConvertible(jsonExpression), valueError);
+ if (!value) {
+ [NSException raise:NSInvalidArgumentException
+ format:@"Invalid property value: %@", @(valueError.message.c_str())];
+ return {};
+ }
+
+ return *value;
+ }
private: // Private utilities for converting from mgl to mbgl values
diff --git a/platform/darwin/src/MGLTileSource.h b/platform/darwin/src/MGLTileSource.h
index caeafcd2f6..2d75fa14d8 100644
--- a/platform/darwin/src/MGLTileSource.h
+++ b/platform/darwin/src/MGLTileSource.h
@@ -41,6 +41,20 @@ extern MGL_EXPORT const MGLTileSourceOption MGLTileSourceOptionMinimumZoomLevel;
*/
extern MGL_EXPORT const MGLTileSourceOption MGLTileSourceOptionMaximumZoomLevel;
+/**
+ An `NSValue` object containing an `MGLCoordinateBounds` struct that specifies
+ the geographic extent of the source.
+
+ If this option is specified, the SDK avoids requesting any tile that falls
+ outside of the coordinate bounds. Otherwise, the SDK requests any tile needed
+ to cover the viewport, as it does by default.
+
+ This option corresponds to the `bounds` key in the
+ <a href="https://github.com/mapbox/tilejson-spec/tree/master/2.1.0">TileJSON</a>
+ specification.
+ */
+extern MGL_EXPORT const MGLTileSourceOption MGLTileSourceOptionCoordinateBounds;
+
#if TARGET_OS_IPHONE
/**
An HTML string defining the buttons to be displayed in an action sheet when the
@@ -103,6 +117,7 @@ extern MGL_EXPORT const MGLTileSourceOption MGLTileSourceOptionAttributionInfos;
*/
extern MGL_EXPORT const MGLTileSourceOption MGLTileSourceOptionTileCoordinateSystem;
+
/**
Tile coordinate systems that determine how tile coordinates in tile URLs are
interpreted.
@@ -127,6 +142,35 @@ typedef NS_ENUM(NSUInteger, MGLTileCoordinateSystem) {
MGLTileCoordinateSystemTMS
};
+
+/**
+ An `NSNumber` object containing an unsigned integer that specifies the encoding
+ formula for raster-dem tilesets. The integer corresponds to one of
+ the constants described in `MGLDEMEncoding`.
+
+ The default value for this option is `MGLDEMEncodingMapbox`.
+
+ This option is not supported by the TileJSON spec.
+ */
+extern MGL_EXPORT const MGLTileSourceOption MGLTileSourceOptionDEMEncoding;
+
+/**
+ The encoding formula used to generate the raster-dem tileset
+*/
+
+typedef NS_ENUM(NSUInteger, MGLDEMEncoding) {
+
+ /**
+ Raster tiles generated with the [Mapbox encoding formula](https://www.mapbox.com/help/access-elevation-data/#mapbox-terrain-rgb).
+ */
+ MGLDEMEncodingMapbox = 0,
+
+ /**
+ Raster tiles generated with the [Mapzen Terrarium encoding formula](https://aws.amazon.com/public-datasets/terrain/).
+ */
+ MGLDEMEncodingTerrarium
+};
+
/**
`MGLTileSource` is a map content source that supplies map tiles to be shown on
the map. The location of and metadata about the tiles are defined either by an
diff --git a/platform/darwin/src/MGLTileSource.mm b/platform/darwin/src/MGLTileSource.mm
index 5644ad9a06..c37812ab8e 100644
--- a/platform/darwin/src/MGLTileSource.mm
+++ b/platform/darwin/src/MGLTileSource.mm
@@ -1,7 +1,9 @@
#import "MGLTileSource_Private.h"
#import "MGLAttributionInfo_Private.h"
+#import "MGLGeometry_Private.h"
#import "NSString+MGLAdditions.h"
+#import "NSValue+MGLAdditions.h"
#if TARGET_OS_IPHONE
#import <UIKit/UIKit.h>
@@ -13,9 +15,11 @@
const MGLTileSourceOption MGLTileSourceOptionMinimumZoomLevel = @"MGLTileSourceOptionMinimumZoomLevel";
const MGLTileSourceOption MGLTileSourceOptionMaximumZoomLevel = @"MGLTileSourceOptionMaximumZoomLevel";
+const MGLTileSourceOption MGLTileSourceOptionCoordinateBounds = @"MGLTileSourceOptionCoordinateBounds";
const MGLTileSourceOption MGLTileSourceOptionAttributionHTMLString = @"MGLTileSourceOptionAttributionHTMLString";
const MGLTileSourceOption MGLTileSourceOptionAttributionInfos = @"MGLTileSourceOptionAttributionInfos";
const MGLTileSourceOption MGLTileSourceOptionTileCoordinateSystem = @"MGLTileSourceOptionTileCoordinateSystem";
+const MGLTileSourceOption MGLTileSourceOptionDEMEncoding = @"MGLTileSourceOptionDEMEncoding";
@implementation MGLTileSource
@@ -70,6 +74,15 @@ mbgl::Tileset MGLTileSetFromTileURLTemplates(NS_ARRAY_OF(NSString *) *tileURLTem
[NSException raise:NSInvalidArgumentException
format:@"MGLTileSourceOptionMinimumZoomLevel must be less than MGLTileSourceOptionMaximumZoomLevel."];
}
+
+ if (NSValue *coordinateBounds = options[MGLTileSourceOptionCoordinateBounds]) {
+ if (![coordinateBounds isKindOfClass:[NSValue class]]
+ && strcmp(coordinateBounds.objCType, @encode(MGLCoordinateBounds)) == 0) {
+ [NSException raise:NSInvalidArgumentException
+ format:@"MGLTileSourceOptionCoordinateBounds must be set to an NSValue containing an MGLCoordinateBounds."];
+ }
+ tileSet.bounds = MGLLatLngBoundsFromCoordinateBounds(coordinateBounds.MGLCoordinateBoundsValue);
+ }
if (NSString *attribution = options[MGLTileSourceOptionAttributionHTMLString]) {
if (![attribution isKindOfClass:[NSString class]]) {
@@ -117,5 +130,22 @@ mbgl::Tileset MGLTileSetFromTileURLTemplates(NS_ARRAY_OF(NSString *) *tileURLTem
}
}
+ if (NSNumber *demEncodingNumber = options[MGLTileSourceOptionDEMEncoding]) {
+ if (![demEncodingNumber isKindOfClass:[NSValue class]]) {
+ [NSException raise:NSInvalidArgumentException
+ format:@"MGLTileSourceOptionDEMEncoding must be set to an NSValue or NSNumber."];
+ }
+ MGLDEMEncoding demEncoding;
+ [demEncodingNumber getValue:&demEncoding];
+ switch (demEncoding) {
+ case MGLDEMEncodingMapbox:
+ tileSet.encoding = mbgl::Tileset::DEMEncoding::Mapbox;
+ break;
+ case MGLDEMEncodingTerrarium:
+ tileSet.encoding = mbgl::Tileset::DEMEncoding::Terrarium;
+ break;
+ }
+ }
+
return tileSet;
}
diff --git a/platform/darwin/src/MGLVectorStyleLayer.h b/platform/darwin/src/MGLVectorStyleLayer.h
index 7780a34c7f..177b1b70f0 100644
--- a/platform/darwin/src/MGLVectorStyleLayer.h
+++ b/platform/darwin/src/MGLVectorStyleLayer.h
@@ -10,10 +10,10 @@ NS_ASSUME_NONNULL_BEGIN
is defined by an `MGLShapeSource` or `MGLVectorSource` object.
Create instances of `MGLCircleStyleLayer`, `MGLFillStyleLayer`,
- `MGLFillExtrusionStyleLayer`, `MGLLineStyleLayer`, and `MGLSymbolStyleLayer` in
- order to use `MGLVectorStyleLayer`'s properties and methods. Do not create
- instances of `MGLVectorStyleLayer` directly, and do not create your own
- subclasses of this class.
+ `MGLFillExtrusionStyleLayer`, `MGLHeatmapStyleLayer`, `MGLLineStyleLayer`, and
+ `MGLSymbolStyleLayer` in order to use `MGLVectorStyleLayer`'s properties and
+ methods. Do not create instances of `MGLVectorStyleLayer` directly, and do not
+ create your own subclasses of this class.
*/
MGL_EXPORT
@interface MGLVectorStyleLayer : MGLForegroundStyleLayer
diff --git a/platform/darwin/src/run_loop.cpp b/platform/darwin/src/run_loop.cpp
index d60a88cf52..0778b004e5 100644
--- a/platform/darwin/src/run_loop.cpp
+++ b/platform/darwin/src/run_loop.cpp
@@ -29,11 +29,8 @@ RunLoop::~RunLoop() {
Scheduler::SetCurrent(nullptr);
}
-void RunLoop::push(std::shared_ptr<WorkTask> task) {
- withMutex([&] {
- queue.push(std::move(task));
- impl->async->send();
- });
+void RunLoop::wake() {
+ impl->async->send();
}
void RunLoop::run() {
diff --git a/platform/darwin/test/MGLCoordinateFormatterTests.m b/platform/darwin/test/MGLCoordinateFormatterTests.m
index ac083fa103..d693f739ec 100644
--- a/platform/darwin/test/MGLCoordinateFormatterTests.m
+++ b/platform/darwin/test/MGLCoordinateFormatterTests.m
@@ -24,7 +24,12 @@
coordinate = CLLocationCoordinate2DMake(38.9131982, -77.0325453144239);
XCTAssertEqualObjects([shortFormatter stringFromCoordinate:coordinate], @"38°54′48″N, 77°1′57″W");
XCTAssertEqualObjects([mediumFormatter stringFromCoordinate:coordinate], @"38°54′48″ north, 77°1′57″ west");
- XCTAssertEqualObjects([longFormatter stringFromCoordinate:coordinate], @"38 degrees, 54 minutes, and 48 seconds north by 77 degrees, 1 minute, and 57 seconds west");
+ if (@available(iOS 9.0, *)) {
+ XCTAssertEqualObjects([longFormatter stringFromCoordinate:coordinate], @"38 degrees, 54 minutes, and 48 seconds north by 77 degrees, 1 minute, and 57 seconds west");
+ } else {
+ // Foundation in iOS 8 does not know how to pluralize coordinates.
+ XCTAssertEqualObjects([longFormatter stringFromCoordinate:coordinate], @"38 degree(s), 54 minute(s), and 48 second(s) north by 77 degree(s), 1 minute(s), and 57 second(s) west");
+ }
shortFormatter.allowsSeconds = NO;
mediumFormatter.allowsSeconds = NO;
@@ -33,7 +38,12 @@
coordinate = CLLocationCoordinate2DMake(38.9131982, -77.0325453144239);
XCTAssertEqualObjects([shortFormatter stringFromCoordinate:coordinate], @"38°55′N, 77°2′W");
XCTAssertEqualObjects([mediumFormatter stringFromCoordinate:coordinate], @"38°55′ north, 77°2′ west");
- XCTAssertEqualObjects([longFormatter stringFromCoordinate:coordinate], @"38 degrees and 55 minutes north by 77 degrees and 2 minutes west");
+ if (@available(iOS 9.0, *)) {
+ XCTAssertEqualObjects([longFormatter stringFromCoordinate:coordinate], @"38 degrees and 55 minutes north by 77 degrees and 2 minutes west");
+ } else {
+ // Foundation in iOS 8 does not know how to pluralize coordinates.
+ XCTAssertEqualObjects([longFormatter stringFromCoordinate:coordinate], @"38 degree(s) and 55 minute(s) north by 77 degree(s) and 2 minute(s) west");
+ }
shortFormatter.allowsMinutes = NO;
mediumFormatter.allowsMinutes = NO;
@@ -42,7 +52,12 @@
coordinate = CLLocationCoordinate2DMake(38.9131982, -77.0325453144239);
XCTAssertEqualObjects([shortFormatter stringFromCoordinate:coordinate], @"39°N, 77°W");
XCTAssertEqualObjects([mediumFormatter stringFromCoordinate:coordinate], @"39° north, 77° west");
- XCTAssertEqualObjects([longFormatter stringFromCoordinate:coordinate], @"39 degrees north by 77 degrees west");
+ if (@available(iOS 9.0, *)) {
+ XCTAssertEqualObjects([longFormatter stringFromCoordinate:coordinate], @"39 degrees north by 77 degrees west");
+ } else {
+ // Foundation in iOS 8 does not know how to pluralize coordinates.
+ XCTAssertEqualObjects([longFormatter stringFromCoordinate:coordinate], @"39 degree(s) north by 77 degree(s) west");
+ }
}
@end
diff --git a/platform/darwin/test/MGLDocumentationExampleTests.swift b/platform/darwin/test/MGLDocumentationExampleTests.swift
index 9bf9924869..a216d9ad1c 100644
--- a/platform/darwin/test/MGLDocumentationExampleTests.swift
+++ b/platform/darwin/test/MGLDocumentationExampleTests.swift
@@ -230,6 +230,24 @@ class MGLDocumentationExampleTests: XCTestCase, MGLMapViewDelegate {
XCTAssertNotNil(mapView.style?.layer(withIdentifier: "buildings"))
}
+
+ func testMGLHeatmapStyleLayer() {
+ let earthquakes = MGLShapeSource(identifier: "earthquakes", url: URL(string: "https://example.com/earthquakes.json")!, options: [:])
+ mapView.style?.addSource(earthquakes)
+
+ //#-example-code
+ let layer = MGLHeatmapStyleLayer(identifier: "earthquake-heat", source: earthquakes)
+ layer.heatmapWeight = NSExpression(format: "FUNCTION(magnitude, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)",
+ [0: 0,
+ 6: 1])
+ layer.heatmapIntensity = NSExpression(format: "FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)",
+ [0: 1,
+ 9: 3])
+ mapView.style?.addLayer(layer)
+ //#-end-example-code
+
+ XCTAssertNotNil(mapView.style?.layer(withIdentifier: "earthquake-heat"))
+ }
func testMGLSymbolStyleLayer() {
let pois = MGLVectorSource(identifier: "pois", configurationURL: URL(string: "https://example.com/style.json")!)
@@ -373,7 +391,7 @@ class MGLDocumentationExampleTests: XCTestCase, MGLMapViewDelegate {
}
//#-end-example-code
- wait(for: [expectation], timeout: 1)
+ wait(for: [expectation], timeout: 5)
}
// For testMGLMapView().
diff --git a/platform/darwin/test/MGLExpressionTests.mm b/platform/darwin/test/MGLExpressionTests.mm
index 821c5dbdb4..a5ed2f7bf5 100644
--- a/platform/darwin/test/MGLExpressionTests.mm
+++ b/platform/darwin/test/MGLExpressionTests.mm
@@ -573,23 +573,27 @@ using namespace std::string_literals;
}
- (void)testConditionalExpressionObject {
- {
- NSPredicate *conditional = [NSPredicate predicateWithFormat:@"1 = 2"];
- NSExpression *trueExpression = [NSExpression expressionForConstantValue:@YES];
- NSExpression *falseExpression = [NSExpression expressionForConstantValue:@NO];
- NSExpression *expression = [NSExpression expressionForConditional:conditional trueExpression:trueExpression falseExpression:falseExpression];
- NSArray *jsonExpression = @[@"case", @[@"==", @1, @2], @YES, @NO];
- XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression);
- XCTAssertEqualObjects([NSExpression expressionWithFormat:@"TERNARY(1 = 2, TRUE, FALSE)"].mgl_jsonExpressionObject, jsonExpression);
- XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @NO);
- XCTAssertEqualObjects([NSExpression mgl_expressionWithJSONObject:jsonExpression], expression);
- }
- {
- NSExpression *expression = [NSExpression expressionWithFormat:@"TERNARY(0 = 1, TRUE, TERNARY(1 = 2, TRUE, FALSE))"];
- NSArray *jsonExpression = @[@"case", @[@"==", @0, @1], @YES, @[@"==", @1, @2], @YES, @NO];
- XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression);
- XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @NO);
- XCTAssertEqualObjects([NSExpression mgl_expressionWithJSONObject:jsonExpression], expression);
+ // FIXME: This test crashes because iOS 8 doesn't have `+[NSExpression expressionForConditional:trueExpression:falseExpression:]`.
+ // https://github.com/mapbox/mapbox-gl-native/issues/11007
+ if (@available(iOS 9.0, *)) {
+ {
+ NSPredicate *conditional = [NSPredicate predicateWithFormat:@"1 = 2"];
+ NSExpression *trueExpression = [NSExpression expressionForConstantValue:@YES];
+ NSExpression *falseExpression = [NSExpression expressionForConstantValue:@NO];
+ NSExpression *expression = [NSExpression expressionForConditional:conditional trueExpression:trueExpression falseExpression:falseExpression];
+ NSArray *jsonExpression = @[@"case", @[@"==", @1, @2], @YES, @NO];
+ XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression);
+ XCTAssertEqualObjects([NSExpression expressionWithFormat:@"TERNARY(1 = 2, TRUE, FALSE)"].mgl_jsonExpressionObject, jsonExpression);
+ XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @NO);
+ XCTAssertEqualObjects([NSExpression mgl_expressionWithJSONObject:jsonExpression], expression);
+ }
+ {
+ NSExpression *expression = [NSExpression expressionWithFormat:@"TERNARY(0 = 1, TRUE, TERNARY(1 = 2, TRUE, FALSE))"];
+ NSArray *jsonExpression = @[@"case", @[@"==", @0, @1], @YES, @[@"==", @1, @2], @YES, @NO];
+ XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression);
+ XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @NO);
+ XCTAssertEqualObjects([NSExpression mgl_expressionWithJSONObject:jsonExpression], expression);
+ }
}
}
diff --git a/platform/darwin/test/MGLFeatureTests.mm b/platform/darwin/test/MGLFeatureTests.mm
index 818ad8200e..14b64be854 100644
--- a/platform/darwin/test/MGLFeatureTests.mm
+++ b/platform/darwin/test/MGLFeatureTests.mm
@@ -298,29 +298,36 @@
}
- (void)testShapeCollectionFeatureGeoJSONDictionary {
- MGLPointAnnotation *pointFeature = [[MGLPointAnnotation alloc] init];
+ MGLPointFeature *pointFeature = [[MGLPointFeature alloc] init];
CLLocationCoordinate2D pointCoordinate = { 10, 10 };
pointFeature.coordinate = pointCoordinate;
CLLocationCoordinate2D coord1 = { 0, 0 };
CLLocationCoordinate2D coord2 = { 10, 10 };
CLLocationCoordinate2D coords[] = { coord1, coord2 };
- MGLPolyline *polyline = [MGLPolyline polylineWithCoordinates:coords count:2];
+ MGLPolylineFeature *polylineFeature = [MGLPolylineFeature polylineWithCoordinates:coords count:2];
+
+ MGLShapeCollectionFeature *shapeCollectionFeature = [MGLShapeCollectionFeature shapeCollectionWithShapes:@[pointFeature, polylineFeature]];
- MGLShapeCollectionFeature *shapeCollectionFeature = [MGLShapeCollectionFeature shapeCollectionWithShapes:@[pointFeature,
- polyline]];
// A GeoJSON feature
NSDictionary *geoJSONFeature = [shapeCollectionFeature geoJSONDictionary];
// it has the correct geometry
NSDictionary *expectedGeometry = @{@"type": @"GeometryCollection",
@"geometries": @[
- @{@"type": @"Point",
- @"coordinates": @[@(pointCoordinate.longitude), @(pointCoordinate.latitude)]},
- @{@"type": @"LineString",
- @"coordinates": @[@[@(coord1.longitude), @(coord1.latitude)],
- @[@(coord2.longitude), @(coord2.latitude)]]}
- ]};
+ @{ @"geometry": @{@"type": @"Point",
+ @"coordinates": @[@(pointCoordinate.longitude), @(pointCoordinate.latitude)]},
+ @"properties": [NSNull null],
+ @"type": @"Feature",
+ },
+ @{ @"geometry": @{@"type": @"LineString",
+ @"coordinates": @[@[@(coord1.longitude), @(coord1.latitude)],
+ @[@(coord2.longitude), @(coord2.latitude)]]},
+ @"properties": [NSNull null],
+ @"type": @"Feature",
+ }
+ ]
+ };
XCTAssertEqualObjects(geoJSONFeature[@"geometry"], expectedGeometry);
// When the shape collection is created with an empty array of shapes
diff --git a/platform/darwin/test/MGLHeatmapColorTests.mm b/platform/darwin/test/MGLHeatmapColorTests.mm
new file mode 100644
index 0000000000..8d44064d94
--- /dev/null
+++ b/platform/darwin/test/MGLHeatmapColorTests.mm
@@ -0,0 +1,61 @@
+#import <Mapbox/Mapbox.h>
+#import <XCTest/XCTest.h>
+
+#import "MGLStyleLayer_Private.h"
+
+#include <mbgl/style/layers/heatmap_layer.hpp>
+
+@interface MGLHeatmapColorTests : XCTestCase <MGLMapViewDelegate>
+@end
+
+@implementation MGLHeatmapColorTests
+
+- (void)testProperties {
+ MGLPointFeature *feature = [[MGLPointFeature alloc] init];
+ MGLShapeSource *source = [[MGLShapeSource alloc] initWithIdentifier:@"sourceID" shape:feature options:nil];
+ MGLHeatmapStyleLayer *layer = [[MGLHeatmapStyleLayer alloc] initWithIdentifier:@"layerID" source:source];
+
+ auto rawLayer = layer.rawLayer->as<mbgl::style::HeatmapLayer>();
+
+ XCTAssertTrue(rawLayer->getHeatmapColor().isUndefined(),
+ @"heatmap-color should be unset initially.");
+ NSExpression *defaultExpression = layer.heatmapColor;
+
+ NSExpression *constantExpression = [NSExpression expressionWithFormat:@"%@", [MGLColor redColor]];
+ layer.heatmapColor = constantExpression;
+
+
+ mbgl::style::PropertyValue<float> propertyValue = { 0xff };
+ XCTAssertEqual(rawLayer->getHeatmapColor().evaluate(0.0), mbgl::Color::red(),
+ @"Setting heatmapColor to a constant value expression should update heatmap-color.");
+ XCTAssertEqualObjects(layer.heatmapColor, constantExpression,
+ @"heatmapColor should round-trip constant value expressions.");
+
+ constantExpression = [NSExpression expressionWithFormat:@"%@", [MGLColor redColor]];
+ NSExpression *constantExpression2 = [NSExpression expressionWithFormat:@"%@", [MGLColor blueColor]];
+ NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($heatmapDensity, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@12: constantExpression2}];
+ layer.heatmapColor = functionExpression;
+
+ XCTAssertEqual(rawLayer->getHeatmapColor().evaluate(11.0), mbgl::Color::red(),
+ @"Setting heatmapColor to an expression depending on $heatmapDensity should update heatmap-color.");
+ XCTAssertEqual(rawLayer->getHeatmapColor().evaluate(12.0), mbgl::Color::blue(),
+ @"Setting heatmapColor to an expression depending on $heatmapDensity should update heatmap-color.");
+ XCTAssertEqualObjects(layer.heatmapColor, functionExpression,
+ @"heatmapColor should round-trip expressions depending on $heatmapDensity.");
+
+ layer.heatmapColor = nil;
+ XCTAssertTrue(rawLayer->getHeatmapColor().isUndefined(),
+ @"Unsetting heatmapColor should return heatmap-color to the default value.");
+ XCTAssertEqualObjects(layer.heatmapColor, defaultExpression,
+ @"heatmapColor should return the default value after being unset.");
+
+ functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}];
+ XCTAssertThrowsSpecificNamed(layer.heatmapColor = functionExpression, NSException, NSInvalidArgumentException, @"MGLHeatmapLayer should raise an exception if a camera expression is applied to heatmapColor.");
+ functionExpression = [NSExpression expressionForKeyPath:@"bogus"];
+ XCTAssertThrowsSpecificNamed(layer.heatmapColor = functionExpression, NSException, NSInvalidArgumentException, @"MGLHeatmapLayer should raise an exception if a data expression is applied to heatmapColor.");
+ functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(bogus, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}];
+ functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}];
+ XCTAssertThrowsSpecificNamed(layer.heatmapColor = functionExpression, NSException, NSInvalidArgumentException, @"MGLHeatmapLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes.");
+}
+
+@end
diff --git a/platform/darwin/test/MGLHeatmapStyleLayerTests.mm b/platform/darwin/test/MGLHeatmapStyleLayerTests.mm
new file mode 100644
index 0000000000..74121affd8
--- /dev/null
+++ b/platform/darwin/test/MGLHeatmapStyleLayerTests.mm
@@ -0,0 +1,296 @@
+// This file is generated.
+// Edit platform/darwin/scripts/generate-style-code.js, then run `make darwin-style-code`.
+
+#import "MGLStyleLayerTests.h"
+#import "../../darwin/src/NSDate+MGLAdditions.h"
+
+#import "MGLStyleLayer_Private.h"
+
+#include <mbgl/style/layers/heatmap_layer.hpp>
+#include <mbgl/style/transition_options.hpp>
+
+@interface MGLHeatmapLayerTests : MGLStyleLayerTests
+@end
+
+@implementation MGLHeatmapLayerTests
+
++ (NSString *)layerType {
+ return @"heatmap";
+}
+
+- (void)testPredicates {
+ MGLPointFeature *feature = [[MGLPointFeature alloc] init];
+ MGLShapeSource *source = [[MGLShapeSource alloc] initWithIdentifier:@"sourceID" shape:feature options:nil];
+ MGLHeatmapStyleLayer *layer = [[MGLHeatmapStyleLayer alloc] initWithIdentifier:@"layerID" source:source];
+
+ XCTAssertNil(layer.sourceLayerIdentifier);
+ layer.sourceLayerIdentifier = @"layerID";
+ XCTAssertEqualObjects(layer.sourceLayerIdentifier, @"layerID");
+ layer.sourceLayerIdentifier = nil;
+ XCTAssertNil(layer.sourceLayerIdentifier);
+
+ XCTAssertNil(layer.predicate);
+ layer.predicate = [NSPredicate predicateWithValue:NO];
+ XCTAssertEqualObjects(layer.predicate, [NSPredicate predicateWithValue:NO]);
+ layer.predicate = nil;
+ XCTAssertNil(layer.predicate);
+}
+
+- (void)testProperties {
+ MGLPointFeature *feature = [[MGLPointFeature alloc] init];
+ MGLShapeSource *source = [[MGLShapeSource alloc] initWithIdentifier:@"sourceID" shape:feature options:nil];
+
+ MGLHeatmapStyleLayer *layer = [[MGLHeatmapStyleLayer alloc] initWithIdentifier:@"layerID" source:source];
+ XCTAssertNotEqual(layer.rawLayer, nullptr);
+ XCTAssertTrue(layer.rawLayer->is<mbgl::style::HeatmapLayer>());
+ auto rawLayer = layer.rawLayer->as<mbgl::style::HeatmapLayer>();
+
+ MGLTransition transitionTest = MGLTransitionMake(5, 4);
+
+
+ // heatmap-intensity
+ {
+ XCTAssertTrue(rawLayer->getHeatmapIntensity().isUndefined(),
+ @"heatmap-intensity should be unset initially.");
+ NSExpression *defaultExpression = layer.heatmapIntensity;
+
+ NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"];
+ layer.heatmapIntensity = constantExpression;
+ mbgl::style::PropertyValue<float> propertyValue = { 0xff };
+ XCTAssertEqual(rawLayer->getHeatmapIntensity(), propertyValue,
+ @"Setting heatmapIntensity to a constant value expression should update heatmap-intensity.");
+ XCTAssertEqualObjects(layer.heatmapIntensity, constantExpression,
+ @"heatmapIntensity should round-trip constant value expressions.");
+
+ constantExpression = [NSExpression expressionWithFormat:@"0xff"];
+ NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}];
+ layer.heatmapIntensity = functionExpression;
+
+ mbgl::style::IntervalStops<float> intervalStops = {{
+ { -INFINITY, 0xff },
+ { 18, 0xff },
+ }};
+ propertyValue = mbgl::style::CameraFunction<float> { intervalStops };
+
+ XCTAssertEqual(rawLayer->getHeatmapIntensity(), propertyValue,
+ @"Setting heatmapIntensity to a camera expression should update heatmap-intensity.");
+ XCTAssertEqualObjects(layer.heatmapIntensity, functionExpression,
+ @"heatmapIntensity should round-trip camera expressions.");
+
+
+
+ layer.heatmapIntensity = nil;
+ XCTAssertTrue(rawLayer->getHeatmapIntensity().isUndefined(),
+ @"Unsetting heatmapIntensity should return heatmap-intensity to the default value.");
+ XCTAssertEqualObjects(layer.heatmapIntensity, defaultExpression,
+ @"heatmapIntensity should return the default value after being unset.");
+
+ functionExpression = [NSExpression expressionForKeyPath:@"bogus"];
+ XCTAssertThrowsSpecificNamed(layer.heatmapIntensity = functionExpression, NSException, NSInvalidArgumentException, @"MGLHeatmapLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes.");
+ functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(bogus, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}];
+ functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}];
+ XCTAssertThrowsSpecificNamed(layer.heatmapIntensity = functionExpression, NSException, NSInvalidArgumentException, @"MGLHeatmapLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes.");
+ // Transition property test
+ layer.heatmapIntensityTransition = transitionTest;
+ auto toptions = rawLayer->getHeatmapIntensityTransition();
+ XCTAssert(toptions.delay && MGLTimeIntervalFromDuration(*toptions.delay) == transitionTest.delay);
+ XCTAssert(toptions.duration && MGLTimeIntervalFromDuration(*toptions.duration) == transitionTest.duration);
+
+ MGLTransition heatmapIntensityTransition = layer.heatmapIntensityTransition;
+ XCTAssertEqual(heatmapIntensityTransition.delay, transitionTest.delay);
+ XCTAssertEqual(heatmapIntensityTransition.duration, transitionTest.duration);
+ }
+
+ // heatmap-opacity
+ {
+ XCTAssertTrue(rawLayer->getHeatmapOpacity().isUndefined(),
+ @"heatmap-opacity should be unset initially.");
+ NSExpression *defaultExpression = layer.heatmapOpacity;
+
+ NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"];
+ layer.heatmapOpacity = constantExpression;
+ mbgl::style::PropertyValue<float> propertyValue = { 0xff };
+ XCTAssertEqual(rawLayer->getHeatmapOpacity(), propertyValue,
+ @"Setting heatmapOpacity to a constant value expression should update heatmap-opacity.");
+ XCTAssertEqualObjects(layer.heatmapOpacity, constantExpression,
+ @"heatmapOpacity should round-trip constant value expressions.");
+
+ constantExpression = [NSExpression expressionWithFormat:@"0xff"];
+ NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}];
+ layer.heatmapOpacity = functionExpression;
+
+ mbgl::style::IntervalStops<float> intervalStops = {{
+ { -INFINITY, 0xff },
+ { 18, 0xff },
+ }};
+ propertyValue = mbgl::style::CameraFunction<float> { intervalStops };
+
+ XCTAssertEqual(rawLayer->getHeatmapOpacity(), propertyValue,
+ @"Setting heatmapOpacity to a camera expression should update heatmap-opacity.");
+ XCTAssertEqualObjects(layer.heatmapOpacity, functionExpression,
+ @"heatmapOpacity should round-trip camera expressions.");
+
+
+
+ layer.heatmapOpacity = nil;
+ XCTAssertTrue(rawLayer->getHeatmapOpacity().isUndefined(),
+ @"Unsetting heatmapOpacity should return heatmap-opacity to the default value.");
+ XCTAssertEqualObjects(layer.heatmapOpacity, defaultExpression,
+ @"heatmapOpacity should return the default value after being unset.");
+
+ functionExpression = [NSExpression expressionForKeyPath:@"bogus"];
+ XCTAssertThrowsSpecificNamed(layer.heatmapOpacity = functionExpression, NSException, NSInvalidArgumentException, @"MGLHeatmapLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes.");
+ functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(bogus, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}];
+ functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}];
+ XCTAssertThrowsSpecificNamed(layer.heatmapOpacity = functionExpression, NSException, NSInvalidArgumentException, @"MGLHeatmapLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes.");
+ // Transition property test
+ layer.heatmapOpacityTransition = transitionTest;
+ auto toptions = rawLayer->getHeatmapOpacityTransition();
+ XCTAssert(toptions.delay && MGLTimeIntervalFromDuration(*toptions.delay) == transitionTest.delay);
+ XCTAssert(toptions.duration && MGLTimeIntervalFromDuration(*toptions.duration) == transitionTest.duration);
+
+ MGLTransition heatmapOpacityTransition = layer.heatmapOpacityTransition;
+ XCTAssertEqual(heatmapOpacityTransition.delay, transitionTest.delay);
+ XCTAssertEqual(heatmapOpacityTransition.duration, transitionTest.duration);
+ }
+
+ // heatmap-radius
+ {
+ XCTAssertTrue(rawLayer->getHeatmapRadius().isUndefined(),
+ @"heatmap-radius should be unset initially.");
+ NSExpression *defaultExpression = layer.heatmapRadius;
+
+ NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"];
+ layer.heatmapRadius = constantExpression;
+ mbgl::style::DataDrivenPropertyValue<float> propertyValue = { 0xff };
+ XCTAssertEqual(rawLayer->getHeatmapRadius(), propertyValue,
+ @"Setting heatmapRadius to a constant value expression should update heatmap-radius.");
+ XCTAssertEqualObjects(layer.heatmapRadius, constantExpression,
+ @"heatmapRadius should round-trip constant value expressions.");
+
+ constantExpression = [NSExpression expressionWithFormat:@"0xff"];
+ NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}];
+ layer.heatmapRadius = functionExpression;
+
+ mbgl::style::IntervalStops<float> intervalStops = {{
+ { -INFINITY, 0xff },
+ { 18, 0xff },
+ }};
+ propertyValue = mbgl::style::CameraFunction<float> { intervalStops };
+
+ XCTAssertEqual(rawLayer->getHeatmapRadius(), propertyValue,
+ @"Setting heatmapRadius to a camera expression should update heatmap-radius.");
+ XCTAssertEqualObjects(layer.heatmapRadius, functionExpression,
+ @"heatmapRadius should round-trip camera expressions.");
+
+ functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(keyName, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@18: constantExpression}];
+ layer.heatmapRadius = functionExpression;
+
+ mbgl::style::ExponentialStops<float> exponentialStops = { {{18, 0xff}}, 1.0 };
+ propertyValue = mbgl::style::SourceFunction<float> { "keyName", exponentialStops };
+
+ XCTAssertEqual(rawLayer->getHeatmapRadius(), propertyValue,
+ @"Setting heatmapRadius to a data expression should update heatmap-radius.");
+ XCTAssertEqualObjects(layer.heatmapRadius, functionExpression,
+ @"heatmapRadius should round-trip data expressions.");
+
+ functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}];
+ layer.heatmapRadius = functionExpression;
+
+ std::map<float, float> innerStops { {18, 0xff} };
+ mbgl::style::CompositeExponentialStops<float> compositeStops { { {10.0, innerStops} }, 1.0 };
+
+ propertyValue = mbgl::style::CompositeFunction<float> { "keyName", compositeStops };
+
+ XCTAssertEqual(rawLayer->getHeatmapRadius(), propertyValue,
+ @"Setting heatmapRadius to a camera-data expression should update heatmap-radius.");
+ XCTAssertEqualObjects(layer.heatmapRadius, functionExpression,
+ @"heatmapRadius should round-trip camera-data expressions.");
+
+
+ layer.heatmapRadius = nil;
+ XCTAssertTrue(rawLayer->getHeatmapRadius().isUndefined(),
+ @"Unsetting heatmapRadius should return heatmap-radius to the default value.");
+ XCTAssertEqualObjects(layer.heatmapRadius, defaultExpression,
+ @"heatmapRadius should return the default value after being unset.");
+ // Transition property test
+ layer.heatmapRadiusTransition = transitionTest;
+ auto toptions = rawLayer->getHeatmapRadiusTransition();
+ XCTAssert(toptions.delay && MGLTimeIntervalFromDuration(*toptions.delay) == transitionTest.delay);
+ XCTAssert(toptions.duration && MGLTimeIntervalFromDuration(*toptions.duration) == transitionTest.duration);
+
+ MGLTransition heatmapRadiusTransition = layer.heatmapRadiusTransition;
+ XCTAssertEqual(heatmapRadiusTransition.delay, transitionTest.delay);
+ XCTAssertEqual(heatmapRadiusTransition.duration, transitionTest.duration);
+ }
+
+ // heatmap-weight
+ {
+ XCTAssertTrue(rawLayer->getHeatmapWeight().isUndefined(),
+ @"heatmap-weight should be unset initially.");
+ NSExpression *defaultExpression = layer.heatmapWeight;
+
+ NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"];
+ layer.heatmapWeight = constantExpression;
+ mbgl::style::DataDrivenPropertyValue<float> propertyValue = { 0xff };
+ XCTAssertEqual(rawLayer->getHeatmapWeight(), propertyValue,
+ @"Setting heatmapWeight to a constant value expression should update heatmap-weight.");
+ XCTAssertEqualObjects(layer.heatmapWeight, constantExpression,
+ @"heatmapWeight should round-trip constant value expressions.");
+
+ constantExpression = [NSExpression expressionWithFormat:@"0xff"];
+ NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}];
+ layer.heatmapWeight = functionExpression;
+
+ mbgl::style::IntervalStops<float> intervalStops = {{
+ { -INFINITY, 0xff },
+ { 18, 0xff },
+ }};
+ propertyValue = mbgl::style::CameraFunction<float> { intervalStops };
+
+ XCTAssertEqual(rawLayer->getHeatmapWeight(), propertyValue,
+ @"Setting heatmapWeight to a camera expression should update heatmap-weight.");
+ XCTAssertEqualObjects(layer.heatmapWeight, functionExpression,
+ @"heatmapWeight should round-trip camera expressions.");
+
+ functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(keyName, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@18: constantExpression}];
+ layer.heatmapWeight = functionExpression;
+
+ mbgl::style::ExponentialStops<float> exponentialStops = { {{18, 0xff}}, 1.0 };
+ propertyValue = mbgl::style::SourceFunction<float> { "keyName", exponentialStops };
+
+ XCTAssertEqual(rawLayer->getHeatmapWeight(), propertyValue,
+ @"Setting heatmapWeight to a data expression should update heatmap-weight.");
+ XCTAssertEqualObjects(layer.heatmapWeight, functionExpression,
+ @"heatmapWeight should round-trip data expressions.");
+
+ functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}];
+ layer.heatmapWeight = functionExpression;
+
+ std::map<float, float> innerStops { {18, 0xff} };
+ mbgl::style::CompositeExponentialStops<float> compositeStops { { {10.0, innerStops} }, 1.0 };
+
+ propertyValue = mbgl::style::CompositeFunction<float> { "keyName", compositeStops };
+
+ XCTAssertEqual(rawLayer->getHeatmapWeight(), propertyValue,
+ @"Setting heatmapWeight to a camera-data expression should update heatmap-weight.");
+ XCTAssertEqualObjects(layer.heatmapWeight, functionExpression,
+ @"heatmapWeight should round-trip camera-data expressions.");
+
+
+ layer.heatmapWeight = nil;
+ XCTAssertTrue(rawLayer->getHeatmapWeight().isUndefined(),
+ @"Unsetting heatmapWeight should return heatmap-weight to the default value.");
+ XCTAssertEqualObjects(layer.heatmapWeight, defaultExpression,
+ @"heatmapWeight should return the default value after being unset.");
+ }
+}
+
+- (void)testPropertyNames {
+ [self testPropertyName:@"heatmap-intensity" isBoolean:NO];
+ [self testPropertyName:@"heatmap-opacity" isBoolean:NO];
+ [self testPropertyName:@"heatmap-radius" isBoolean:NO];
+ [self testPropertyName:@"heatmap-weight" isBoolean:NO];
+}
+
+@end
diff --git a/platform/darwin/test/MGLStyleLayerTests.mm.ejs b/platform/darwin/test/MGLStyleLayerTests.mm.ejs
index e26c63e662..e17501ed18 100644
--- a/platform/darwin/test/MGLStyleLayerTests.mm.ejs
+++ b/platform/darwin/test/MGLStyleLayerTests.mm.ejs
@@ -59,6 +59,7 @@
MGLTransition transitionTest = MGLTransitionMake(5, 4);
<% for (const property of properties) { -%>
+<% if (property.name === 'heatmap-color') continue; -%>
// <%- originalPropertyName(property) %>
{
@@ -151,6 +152,7 @@
- (void)testPropertyNames {
<% for (const property of properties) { -%>
+<% if (property.name === 'heatmap-color') continue; -%>
[self testPropertyName:@"<%- property.getter || property.name %>" isBoolean:<%- property.type === 'boolean' ? 'YES' : 'NO' %>];
<% } -%>
}
diff --git a/platform/darwin/test/MGLTileSetTests.mm b/platform/darwin/test/MGLTileSetTests.mm
index 40eab5f974..74c84184e1 100644
--- a/platform/darwin/test/MGLTileSetTests.mm
+++ b/platform/darwin/test/MGLTileSetTests.mm
@@ -2,6 +2,7 @@
#import <Mapbox/Mapbox.h>
#import "MGLTileSource_Private.h"
+#import "MGLGeometry_Private.h"
#include <mbgl/util/tileset.hpp>
@@ -40,6 +41,19 @@
XCTAssertEqual(tileSet.zoomRange.min, 1);
XCTAssertEqual(tileSet.zoomRange.max, 2);
+ // when the tile set has a bounds set
+ MGLCoordinateBounds bounds = MGLCoordinateBoundsMake(CLLocationCoordinate2DMake(12, 34), CLLocationCoordinate2DMake(56, 78));
+ tileSet = MGLTileSetFromTileURLTemplates(@[@"tile.1"], @{
+ MGLTileSourceOptionCoordinateBounds: @(bounds),
+ });
+
+ // the mbgl object reflects the set values for the bounds
+ XCTAssert(!!tileSet.bounds, @"The bounds are set after setting the bounds");
+ if (tileSet.bounds) {
+ MGLCoordinateBounds actual = MGLCoordinateBoundsFromLatLngBounds(*tileSet.bounds);
+ XCTAssert(MGLCoordinateBoundsEqualToCoordinateBounds(bounds, actual), @"The bounds round-trip");
+ }
+
// when the tile set has an attribution
NSString *attribution = @"my tileset © ©️🎈";
tileSet = MGLTileSetFromTileURLTemplates(tileURLTemplates, @{
@@ -88,6 +102,23 @@
// the scheme is reflected by the mbgl tileset
XCTAssertEqual(tileSet.scheme, mbgl::Tileset::Scheme::TMS);
+
+ // when the dem enciding is changed using an NSNumber
+ tileSet = MGLTileSetFromTileURLTemplates(tileURLTemplates, @{
+ MGLTileSourceOptionDEMEncoding: @(MGLDEMEncodingTerrarium),
+ });
+
+ // the encoding is reflected by the mbgl tileset
+ XCTAssertEqual(tileSet.encoding, mbgl::Tileset::DEMEncoding::Terrarium);
+
+ // when the dem enciding is changed using an NSValue
+ MGLDEMEncoding terrarium = MGLDEMEncodingTerrarium;
+ tileSet = MGLTileSetFromTileURLTemplates(tileURLTemplates, @{
+ MGLTileSourceOptionDEMEncoding: [NSValue value:&terrarium withObjCType:@encode(MGLDEMEncoding)],
+ });
+
+ // the encoding is reflected by the mbgl tileset
+ XCTAssertEqual(tileSet.encoding, mbgl::Tileset::DEMEncoding::Terrarium);
}
- (void)testInvalidTileSet {
diff --git a/platform/default/mbgl/storage/offline_database.cpp b/platform/default/mbgl/storage/offline_database.cpp
index 65c2097182..4611e69f43 100644
--- a/platform/default/mbgl/storage/offline_database.cpp
+++ b/platform/default/mbgl/storage/offline_database.cpp
@@ -10,11 +10,6 @@
namespace mbgl {
-OfflineDatabase::Statement::~Statement() {
- stmt.reset();
- stmt.clearBindings();
-}
-
OfflineDatabase::OfflineDatabase(std::string path_, uint64_t maximumCacheSize_)
: path(std::move(path_)),
maximumCacheSize(maximumCacheSize_) {
@@ -28,7 +23,7 @@ OfflineDatabase::~OfflineDatabase() {
statements.clear();
db.reset();
} catch (mapbox::sqlite::Exception& ex) {
- Log::Error(Event::Database, ex.code, ex.what());
+ Log::Error(Event::Database, (int)ex.code, ex.what());
}
}
@@ -57,13 +52,13 @@ void OfflineDatabase::ensureSchema() {
removeExisting();
connect(mapbox::sqlite::ReadWrite | mapbox::sqlite::Create);
} catch (mapbox::sqlite::Exception& ex) {
- if (ex.code != mapbox::sqlite::Exception::Code::CANTOPEN && ex.code != mapbox::sqlite::Exception::Code::NOTADB) {
+ if (ex.code != mapbox::sqlite::ResultCode::CantOpen && ex.code != mapbox::sqlite::ResultCode::NotADB) {
Log::Error(Event::Database, "Unexpected error connecting to database: %s", ex.what());
throw;
}
try {
- if (ex.code == mapbox::sqlite::Exception::Code::NOTADB) {
+ if (ex.code == mapbox::sqlite::ResultCode::NotADB) {
removeExisting();
}
connect(mapbox::sqlite::ReadWrite | mapbox::sqlite::Create);
@@ -92,9 +87,7 @@ void OfflineDatabase::ensureSchema() {
}
int OfflineDatabase::userVersion() {
- auto stmt = db->prepare("PRAGMA user_version");
- stmt.run();
- return stmt.get<int>(0);
+ return static_cast<int>(getPragma<int64_t>("PRAGMA user_version"));
}
void OfflineDatabase::removeExisting() {
@@ -135,14 +128,12 @@ void OfflineDatabase::migrateToVersion6() {
transaction.commit();
}
-OfflineDatabase::Statement OfflineDatabase::getStatement(const char * sql) {
+mapbox::sqlite::Statement& OfflineDatabase::getStatement(const char* sql) {
auto it = statements.find(sql);
-
- if (it != statements.end()) {
- return Statement(*it->second);
+ if (it == statements.end()) {
+ it = statements.emplace(sql, std::make_unique<mapbox::sqlite::Statement>(*db, sql)).first;
}
-
- return Statement(*statements.emplace(sql, std::make_unique<mapbox::sqlite::Statement>(db->prepare(sql))).first->second);
+ return *it->second;
}
optional<Response> OfflineDatabase::get(const Resource& resource) {
@@ -209,41 +200,40 @@ std::pair<bool, uint64_t> OfflineDatabase::putInternal(const Resource& resource,
}
optional<std::pair<Response, uint64_t>> OfflineDatabase::getResource(const Resource& resource) {
- // clang-format off
- Statement accessedStmt = getStatement(
- "UPDATE resources SET accessed = ?1 WHERE url = ?2");
- // clang-format on
-
- accessedStmt->bind(1, util::now());
- accessedStmt->bind(2, resource.url);
- accessedStmt->run();
+ // Update accessed timestamp used for LRU eviction.
+ {
+ mapbox::sqlite::Query accessedQuery{ getStatement("UPDATE resources SET accessed = ?1 WHERE url = ?2") };
+ accessedQuery.bind(1, util::now());
+ accessedQuery.bind(2, resource.url);
+ accessedQuery.run();
+ }
// clang-format off
- Statement stmt = getStatement(
+ mapbox::sqlite::Query query{ getStatement(
// 0 1 2 3 4 5
"SELECT etag, expires, must_revalidate, modified, data, compressed "
"FROM resources "
- "WHERE url = ?");
+ "WHERE url = ?") };
// clang-format on
- stmt->bind(1, resource.url);
+ query.bind(1, resource.url);
- if (!stmt->run()) {
+ if (!query.run()) {
return {};
}
Response response;
uint64_t size = 0;
- response.etag = stmt->get<optional<std::string>>(0);
- response.expires = stmt->get<optional<Timestamp>>(1);
- response.mustRevalidate = stmt->get<bool>(2);
- response.modified = stmt->get<optional<Timestamp>>(3);
+ response.etag = query.get<optional<std::string>>(0);
+ response.expires = query.get<optional<Timestamp>>(1);
+ response.mustRevalidate = query.get<bool>(2);
+ response.modified = query.get<optional<Timestamp>>(3);
- optional<std::string> data = stmt->get<optional<std::string>>(4);
+ auto data = query.get<optional<std::string>>(4);
if (!data) {
response.noContent = true;
- } else if (stmt->get<bool>(5)) {
+ } else if (query.get<bool>(5)) {
response.data = std::make_shared<std::string>(util::decompress(*data));
size = data->length();
} else {
@@ -255,16 +245,13 @@ optional<std::pair<Response, uint64_t>> OfflineDatabase::getResource(const Resou
}
optional<int64_t> OfflineDatabase::hasResource(const Resource& resource) {
- // clang-format off
- Statement stmt = getStatement("SELECT length(data) FROM resources WHERE url = ?");
- // clang-format on
-
- stmt->bind(1, resource.url);
- if (!stmt->run()) {
+ mapbox::sqlite::Query query{ getStatement("SELECT length(data) FROM resources WHERE url = ?") };
+ query.bind(1, resource.url);
+ if (!query.run()) {
return {};
}
- return stmt->get<optional<int64_t>>(0);
+ return query.get<optional<int64_t>>(0);
}
bool OfflineDatabase::putResource(const Resource& resource,
@@ -273,19 +260,19 @@ bool OfflineDatabase::putResource(const Resource& resource,
bool compressed) {
if (response.notModified) {
// clang-format off
- Statement update = getStatement(
+ mapbox::sqlite::Query notModifiedQuery{ getStatement(
"UPDATE resources "
"SET accessed = ?1, "
" expires = ?2, "
" must_revalidate = ?3 "
- "WHERE url = ?4 ");
+ "WHERE url = ?4 ") };
// clang-format on
- update->bind(1, util::now());
- update->bind(2, response.expires);
- update->bind(3, response.mustRevalidate);
- update->bind(4, resource.url);
- update->run();
+ notModifiedQuery.bind(1, util::now());
+ notModifiedQuery.bind(2, response.expires);
+ notModifiedQuery.bind(3, response.mustRevalidate);
+ notModifiedQuery.bind(4, resource.url);
+ notModifiedQuery.run();
return false;
}
@@ -296,7 +283,7 @@ bool OfflineDatabase::putResource(const Resource& resource,
mapbox::sqlite::Transaction transaction(*db, mapbox::sqlite::Transaction::Immediate);
// clang-format off
- Statement update = getStatement(
+ mapbox::sqlite::Query updateQuery{ getStatement(
"UPDATE resources "
"SET kind = ?1, "
" etag = ?2, "
@@ -306,81 +293,83 @@ bool OfflineDatabase::putResource(const Resource& resource,
" accessed = ?6, "
" data = ?7, "
" compressed = ?8 "
- "WHERE url = ?9 ");
+ "WHERE url = ?9 ") };
// clang-format on
- update->bind(1, int(resource.kind));
- update->bind(2, response.etag);
- update->bind(3, response.expires);
- update->bind(4, response.mustRevalidate);
- update->bind(5, response.modified);
- update->bind(6, util::now());
- update->bind(9, resource.url);
+ updateQuery.bind(1, int(resource.kind));
+ updateQuery.bind(2, response.etag);
+ updateQuery.bind(3, response.expires);
+ updateQuery.bind(4, response.mustRevalidate);
+ updateQuery.bind(5, response.modified);
+ updateQuery.bind(6, util::now());
+ updateQuery.bind(9, resource.url);
if (response.noContent) {
- update->bind(7, nullptr);
- update->bind(8, false);
+ updateQuery.bind(7, nullptr);
+ updateQuery.bind(8, false);
} else {
- update->bindBlob(7, data.data(), data.size(), false);
- update->bind(8, compressed);
+ updateQuery.bindBlob(7, data.data(), data.size(), false);
+ updateQuery.bind(8, compressed);
}
- update->run();
- if (update->changes() != 0) {
+ updateQuery.run();
+ if (updateQuery.changes() != 0) {
transaction.commit();
return false;
}
// clang-format off
- Statement insert = getStatement(
+ mapbox::sqlite::Query insertQuery{ getStatement(
"INSERT INTO resources (url, kind, etag, expires, must_revalidate, modified, accessed, data, compressed) "
- "VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9) ");
+ "VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9) ") };
// clang-format on
- insert->bind(1, resource.url);
- insert->bind(2, int(resource.kind));
- insert->bind(3, response.etag);
- insert->bind(4, response.expires);
- insert->bind(5, response.mustRevalidate);
- insert->bind(6, response.modified);
- insert->bind(7, util::now());
+ insertQuery.bind(1, resource.url);
+ insertQuery.bind(2, int(resource.kind));
+ insertQuery.bind(3, response.etag);
+ insertQuery.bind(4, response.expires);
+ insertQuery.bind(5, response.mustRevalidate);
+ insertQuery.bind(6, response.modified);
+ insertQuery.bind(7, util::now());
if (response.noContent) {
- insert->bind(8, nullptr);
- insert->bind(9, false);
+ insertQuery.bind(8, nullptr);
+ insertQuery.bind(9, false);
} else {
- insert->bindBlob(8, data.data(), data.size(), false);
- insert->bind(9, compressed);
+ insertQuery.bindBlob(8, data.data(), data.size(), false);
+ insertQuery.bind(9, compressed);
}
- insert->run();
+ insertQuery.run();
transaction.commit();
return true;
}
optional<std::pair<Response, uint64_t>> OfflineDatabase::getTile(const Resource::TileData& tile) {
- // clang-format off
- Statement accessedStmt = getStatement(
- "UPDATE tiles "
- "SET accessed = ?1 "
- "WHERE url_template = ?2 "
- " AND pixel_ratio = ?3 "
- " AND x = ?4 "
- " AND y = ?5 "
- " AND z = ?6 ");
- // clang-format on
+ {
+ // clang-format off
+ mapbox::sqlite::Query accessedQuery{ getStatement(
+ "UPDATE tiles "
+ "SET accessed = ?1 "
+ "WHERE url_template = ?2 "
+ " AND pixel_ratio = ?3 "
+ " AND x = ?4 "
+ " AND y = ?5 "
+ " AND z = ?6 ") };
+ // clang-format on
- accessedStmt->bind(1, util::now());
- accessedStmt->bind(2, tile.urlTemplate);
- accessedStmt->bind(3, tile.pixelRatio);
- accessedStmt->bind(4, tile.x);
- accessedStmt->bind(5, tile.y);
- accessedStmt->bind(6, tile.z);
- accessedStmt->run();
+ accessedQuery.bind(1, util::now());
+ accessedQuery.bind(2, tile.urlTemplate);
+ accessedQuery.bind(3, tile.pixelRatio);
+ accessedQuery.bind(4, tile.x);
+ accessedQuery.bind(5, tile.y);
+ accessedQuery.bind(6, tile.z);
+ accessedQuery.run();
+ }
// clang-format off
- Statement stmt = getStatement(
+ mapbox::sqlite::Query query{ getStatement(
// 0 1 2, 3, 4, 5
"SELECT etag, expires, must_revalidate, modified, data, compressed "
"FROM tiles "
@@ -388,31 +377,31 @@ optional<std::pair<Response, uint64_t>> OfflineDatabase::getTile(const Resource:
" AND pixel_ratio = ?2 "
" AND x = ?3 "
" AND y = ?4 "
- " AND z = ?5 ");
+ " AND z = ?5 ") };
// clang-format on
- stmt->bind(1, tile.urlTemplate);
- stmt->bind(2, tile.pixelRatio);
- stmt->bind(3, tile.x);
- stmt->bind(4, tile.y);
- stmt->bind(5, tile.z);
+ query.bind(1, tile.urlTemplate);
+ query.bind(2, tile.pixelRatio);
+ query.bind(3, tile.x);
+ query.bind(4, tile.y);
+ query.bind(5, tile.z);
- if (!stmt->run()) {
+ if (!query.run()) {
return {};
}
Response response;
uint64_t size = 0;
- response.etag = stmt->get<optional<std::string>>(0);
- response.expires = stmt->get<optional<Timestamp>>(1);
- response.mustRevalidate = stmt->get<bool>(2);
- response.modified = stmt->get<optional<Timestamp>>(3);
+ response.etag = query.get<optional<std::string>>(0);
+ response.expires = query.get<optional<Timestamp>>(1);
+ response.mustRevalidate = query.get<bool>(2);
+ response.modified = query.get<optional<Timestamp>>(3);
- optional<std::string> data = stmt->get<optional<std::string>>(4);
+ optional<std::string> data = query.get<optional<std::string>>(4);
if (!data) {
response.noContent = true;
- } else if (stmt->get<bool>(5)) {
+ } else if (query.get<bool>(5)) {
response.data = std::make_shared<std::string>(util::decompress(*data));
size = data->length();
} else {
@@ -425,27 +414,27 @@ optional<std::pair<Response, uint64_t>> OfflineDatabase::getTile(const Resource:
optional<int64_t> OfflineDatabase::hasTile(const Resource::TileData& tile) {
// clang-format off
- Statement stmt = getStatement(
+ mapbox::sqlite::Query size{ getStatement(
"SELECT length(data) "
"FROM tiles "
"WHERE url_template = ?1 "
" AND pixel_ratio = ?2 "
" AND x = ?3 "
" AND y = ?4 "
- " AND z = ?5 ");
+ " AND z = ?5 ") };
// clang-format on
- stmt->bind(1, tile.urlTemplate);
- stmt->bind(2, tile.pixelRatio);
- stmt->bind(3, tile.x);
- stmt->bind(4, tile.y);
- stmt->bind(5, tile.z);
+ size.bind(1, tile.urlTemplate);
+ size.bind(2, tile.pixelRatio);
+ size.bind(3, tile.x);
+ size.bind(4, tile.y);
+ size.bind(5, tile.z);
- if (!stmt->run()) {
+ if (!size.run()) {
return {};
}
- return stmt->get<optional<int64_t>>(0);
+ return size.get<optional<int64_t>>(0);
}
bool OfflineDatabase::putTile(const Resource::TileData& tile,
@@ -454,7 +443,7 @@ bool OfflineDatabase::putTile(const Resource::TileData& tile,
bool compressed) {
if (response.notModified) {
// clang-format off
- Statement update = getStatement(
+ mapbox::sqlite::Query notModifiedQuery{ getStatement(
"UPDATE tiles "
"SET accessed = ?1, "
" expires = ?2, "
@@ -463,18 +452,18 @@ bool OfflineDatabase::putTile(const Resource::TileData& tile,
" AND pixel_ratio = ?5 "
" AND x = ?6 "
" AND y = ?7 "
- " AND z = ?8 ");
+ " AND z = ?8 ") };
// clang-format on
- update->bind(1, util::now());
- update->bind(2, response.expires);
- update->bind(3, response.mustRevalidate);
- update->bind(4, tile.urlTemplate);
- update->bind(5, tile.pixelRatio);
- update->bind(6, tile.x);
- update->bind(7, tile.y);
- update->bind(8, tile.z);
- update->run();
+ notModifiedQuery.bind(1, util::now());
+ notModifiedQuery.bind(2, response.expires);
+ notModifiedQuery.bind(3, response.mustRevalidate);
+ notModifiedQuery.bind(4, tile.urlTemplate);
+ notModifiedQuery.bind(5, tile.pixelRatio);
+ notModifiedQuery.bind(6, tile.x);
+ notModifiedQuery.bind(7, tile.y);
+ notModifiedQuery.bind(8, tile.z);
+ notModifiedQuery.run();
return false;
}
@@ -485,7 +474,7 @@ bool OfflineDatabase::putTile(const Resource::TileData& tile,
mapbox::sqlite::Transaction transaction(*db, mapbox::sqlite::Transaction::Immediate);
// clang-format off
- Statement update = getStatement(
+ mapbox::sqlite::Query updateQuery{ getStatement(
"UPDATE tiles "
"SET modified = ?1, "
" etag = ?2, "
@@ -498,78 +487,75 @@ bool OfflineDatabase::putTile(const Resource::TileData& tile,
" AND pixel_ratio = ?9 "
" AND x = ?10 "
" AND y = ?11 "
- " AND z = ?12 ");
+ " AND z = ?12 ") };
// clang-format on
- update->bind(1, response.modified);
- update->bind(2, response.etag);
- update->bind(3, response.expires);
- update->bind(4, response.mustRevalidate);
- update->bind(5, util::now());
- update->bind(8, tile.urlTemplate);
- update->bind(9, tile.pixelRatio);
- update->bind(10, tile.x);
- update->bind(11, tile.y);
- update->bind(12, tile.z);
+ updateQuery.bind(1, response.modified);
+ updateQuery.bind(2, response.etag);
+ updateQuery.bind(3, response.expires);
+ updateQuery.bind(4, response.mustRevalidate);
+ updateQuery.bind(5, util::now());
+ updateQuery.bind(8, tile.urlTemplate);
+ updateQuery.bind(9, tile.pixelRatio);
+ updateQuery.bind(10, tile.x);
+ updateQuery.bind(11, tile.y);
+ updateQuery.bind(12, tile.z);
if (response.noContent) {
- update->bind(6, nullptr);
- update->bind(7, false);
+ updateQuery.bind(6, nullptr);
+ updateQuery.bind(7, false);
} else {
- update->bindBlob(6, data.data(), data.size(), false);
- update->bind(7, compressed);
+ updateQuery.bindBlob(6, data.data(), data.size(), false);
+ updateQuery.bind(7, compressed);
}
- update->run();
- if (update->changes() != 0) {
+ updateQuery.run();
+ if (updateQuery.changes() != 0) {
transaction.commit();
return false;
}
// clang-format off
- Statement insert = getStatement(
+ mapbox::sqlite::Query insertQuery{ getStatement(
"INSERT INTO tiles (url_template, pixel_ratio, x, y, z, modified, must_revalidate, etag, expires, accessed, data, compressed) "
- "VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12)");
+ "VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12)") };
// clang-format on
- insert->bind(1, tile.urlTemplate);
- insert->bind(2, tile.pixelRatio);
- insert->bind(3, tile.x);
- insert->bind(4, tile.y);
- insert->bind(5, tile.z);
- insert->bind(6, response.modified);
- insert->bind(7, response.mustRevalidate);
- insert->bind(8, response.etag);
- insert->bind(9, response.expires);
- insert->bind(10, util::now());
+ insertQuery.bind(1, tile.urlTemplate);
+ insertQuery.bind(2, tile.pixelRatio);
+ insertQuery.bind(3, tile.x);
+ insertQuery.bind(4, tile.y);
+ insertQuery.bind(5, tile.z);
+ insertQuery.bind(6, response.modified);
+ insertQuery.bind(7, response.mustRevalidate);
+ insertQuery.bind(8, response.etag);
+ insertQuery.bind(9, response.expires);
+ insertQuery.bind(10, util::now());
if (response.noContent) {
- insert->bind(11, nullptr);
- insert->bind(12, false);
+ insertQuery.bind(11, nullptr);
+ insertQuery.bind(12, false);
} else {
- insert->bindBlob(11, data.data(), data.size(), false);
- insert->bind(12, compressed);
+ insertQuery.bindBlob(11, data.data(), data.size(), false);
+ insertQuery.bind(12, compressed);
}
- insert->run();
+ insertQuery.run();
transaction.commit();
return true;
}
std::vector<OfflineRegion> OfflineDatabase::listRegions() {
- // clang-format off
- Statement stmt = getStatement(
- "SELECT id, definition, description FROM regions");
- // clang-format on
+ mapbox::sqlite::Query query{ getStatement("SELECT id, definition, description FROM regions") };
std::vector<OfflineRegion> result;
- while (stmt->run()) {
+ while (query.run()) {
result.push_back(OfflineRegion(
- stmt->get<int64_t>(0),
- decodeOfflineRegionDefinition(stmt->get<std::string>(1)),
- stmt->get<std::vector<uint8_t>>(2)));
+ query.get<int64_t>(0),
+ decodeOfflineRegionDefinition(query.get<std::string>(1)),
+ query.get<std::vector<uint8_t>>(2)));
}
return result;
@@ -578,39 +564,37 @@ std::vector<OfflineRegion> OfflineDatabase::listRegions() {
OfflineRegion OfflineDatabase::createRegion(const OfflineRegionDefinition& definition,
const OfflineRegionMetadata& metadata) {
// clang-format off
- Statement stmt = getStatement(
+ mapbox::sqlite::Query query{ getStatement(
"INSERT INTO regions (definition, description) "
- "VALUES (?1, ?2) ");
+ "VALUES (?1, ?2) ") };
// clang-format on
- stmt->bind(1, encodeOfflineRegionDefinition(definition));
- stmt->bindBlob(2, metadata);
- stmt->run();
+ query.bind(1, encodeOfflineRegionDefinition(definition));
+ query.bindBlob(2, metadata);
+ query.run();
- return OfflineRegion(stmt->lastInsertRowId(), definition, metadata);
+ return OfflineRegion(query.lastInsertRowId(), definition, metadata);
}
OfflineRegionMetadata OfflineDatabase::updateMetadata(const int64_t regionID, const OfflineRegionMetadata& metadata) {
// clang-format off
- Statement stmt = getStatement(
- "UPDATE regions SET description = ?1"
- "WHERE id = ?2");
+ mapbox::sqlite::Query query{ getStatement(
+ "UPDATE regions SET description = ?1 "
+ "WHERE id = ?2") };
// clang-format on
- stmt->bindBlob(1, metadata);
- stmt->bind(2, regionID);
- stmt->run();
+ query.bindBlob(1, metadata);
+ query.bind(2, regionID);
+ query.run();
return metadata;
}
void OfflineDatabase::deleteRegion(OfflineRegion&& region) {
- // clang-format off
- Statement stmt = getStatement(
- "DELETE FROM regions WHERE id = ?");
- // clang-format on
-
- stmt->bind(1, region.getID());
- stmt->run();
+ {
+ mapbox::sqlite::Query query{ getStatement("DELETE FROM regions WHERE id = ?") };
+ query.bind(1, region.getID());
+ query.run();
+ }
evict(0);
db->exec("PRAGMA incremental_vacuum");
@@ -656,7 +640,7 @@ uint64_t OfflineDatabase::putRegionResource(int64_t regionID, const Resource& re
bool OfflineDatabase::markUsed(int64_t regionID, const Resource& resource) {
if (resource.kind == Resource::Kind::Tile) {
// clang-format off
- Statement insert = getStatement(
+ mapbox::sqlite::Query insertQuery{ getStatement(
"INSERT OR IGNORE INTO region_tiles (region_id, tile_id) "
"SELECT ?1, tiles.id "
"FROM tiles "
@@ -664,24 +648,24 @@ bool OfflineDatabase::markUsed(int64_t regionID, const Resource& resource) {
" AND pixel_ratio = ?3 "
" AND x = ?4 "
" AND y = ?5 "
- " AND z = ?6 ");
+ " AND z = ?6 ") };
// clang-format on
const Resource::TileData& tile = *resource.tileData;
- insert->bind(1, regionID);
- insert->bind(2, tile.urlTemplate);
- insert->bind(3, tile.pixelRatio);
- insert->bind(4, tile.x);
- insert->bind(5, tile.y);
- insert->bind(6, tile.z);
- insert->run();
-
- if (insert->changes() == 0) {
+ insertQuery.bind(1, regionID);
+ insertQuery.bind(2, tile.urlTemplate);
+ insertQuery.bind(3, tile.pixelRatio);
+ insertQuery.bind(4, tile.x);
+ insertQuery.bind(5, tile.y);
+ insertQuery.bind(6, tile.z);
+ insertQuery.run();
+
+ if (insertQuery.changes() == 0) {
return false;
}
// clang-format off
- Statement select = getStatement(
+ mapbox::sqlite::Query selectQuery{ getStatement(
"SELECT region_id "
"FROM region_tiles, tiles "
"WHERE region_id != ?1 "
@@ -690,58 +674,54 @@ bool OfflineDatabase::markUsed(int64_t regionID, const Resource& resource) {
" AND x = ?4 "
" AND y = ?5 "
" AND z = ?6 "
- "LIMIT 1 ");
+ "LIMIT 1 ") };
// clang-format on
- select->bind(1, regionID);
- select->bind(2, tile.urlTemplate);
- select->bind(3, tile.pixelRatio);
- select->bind(4, tile.x);
- select->bind(5, tile.y);
- select->bind(6, tile.z);
- return !select->run();
+ selectQuery.bind(1, regionID);
+ selectQuery.bind(2, tile.urlTemplate);
+ selectQuery.bind(3, tile.pixelRatio);
+ selectQuery.bind(4, tile.x);
+ selectQuery.bind(5, tile.y);
+ selectQuery.bind(6, tile.z);
+ return !selectQuery.run();
} else {
// clang-format off
- Statement insert = getStatement(
+ mapbox::sqlite::Query insertQuery{ getStatement(
"INSERT OR IGNORE INTO region_resources (region_id, resource_id) "
"SELECT ?1, resources.id "
"FROM resources "
- "WHERE resources.url = ?2 ");
+ "WHERE resources.url = ?2 ") };
// clang-format on
- insert->bind(1, regionID);
- insert->bind(2, resource.url);
- insert->run();
+ insertQuery.bind(1, regionID);
+ insertQuery.bind(2, resource.url);
+ insertQuery.run();
- if (insert->changes() == 0) {
+ if (insertQuery.changes() == 0) {
return false;
}
// clang-format off
- Statement select = getStatement(
+ mapbox::sqlite::Query selectQuery{ getStatement(
"SELECT region_id "
"FROM region_resources, resources "
"WHERE region_id != ?1 "
" AND resources.url = ?2 "
- "LIMIT 1 ");
+ "LIMIT 1 ") };
// clang-format on
- select->bind(1, regionID);
- select->bind(2, resource.url);
- return !select->run();
+ selectQuery.bind(1, regionID);
+ selectQuery.bind(2, resource.url);
+ return !selectQuery.run();
}
}
OfflineRegionDefinition OfflineDatabase::getRegionDefinition(int64_t regionID) {
- // clang-format off
- Statement stmt = getStatement(
- "SELECT definition FROM regions WHERE id = ?1");
- // clang-format on
-
- stmt->bind(1, regionID);
- stmt->run();
+ mapbox::sqlite::Query query{ getStatement("SELECT definition FROM regions WHERE id = ?1") };
+ query.bind(1, regionID);
+ query.run();
- return decodeOfflineRegionDefinition(stmt->get<std::string>(0));
+ return decodeOfflineRegionDefinition(query.get<std::string>(0));
}
OfflineRegionStatus OfflineDatabase::getRegionCompletedStatus(int64_t regionID) {
@@ -760,35 +740,35 @@ OfflineRegionStatus OfflineDatabase::getRegionCompletedStatus(int64_t regionID)
std::pair<int64_t, int64_t> OfflineDatabase::getCompletedResourceCountAndSize(int64_t regionID) {
// clang-format off
- Statement stmt = getStatement(
+ mapbox::sqlite::Query query{ getStatement(
"SELECT COUNT(*), SUM(LENGTH(data)) "
"FROM region_resources, resources "
"WHERE region_id = ?1 "
- "AND resource_id = resources.id ");
+ "AND resource_id = resources.id ") };
// clang-format on
- stmt->bind(1, regionID);
- stmt->run();
- return { stmt->get<int64_t>(0), stmt->get<int64_t>(1) };
+ query.bind(1, regionID);
+ query.run();
+ return { query.get<int64_t>(0), query.get<int64_t>(1) };
}
std::pair<int64_t, int64_t> OfflineDatabase::getCompletedTileCountAndSize(int64_t regionID) {
// clang-format off
- Statement stmt = getStatement(
+ mapbox::sqlite::Query query{ getStatement(
"SELECT COUNT(*), SUM(LENGTH(data)) "
"FROM region_tiles, tiles "
"WHERE region_id = ?1 "
- "AND tile_id = tiles.id ");
+ "AND tile_id = tiles.id ") };
// clang-format on
- stmt->bind(1, regionID);
- stmt->run();
- return { stmt->get<int64_t>(0), stmt->get<int64_t>(1) };
+ query.bind(1, regionID);
+ query.run();
+ return { query.get<int64_t>(0), query.get<int64_t>(1) };
}
template <class T>
-T OfflineDatabase::getPragma(const char * sql) {
- Statement stmt = getStatement(sql);
- stmt->run();
- return stmt->get<T>(0);
+T OfflineDatabase::getPragma(const char* sql) {
+ mapbox::sqlite::Query query{ getStatement(sql) };
+ query.run();
+ return query.get<T>(0);
}
// Remove least-recently used resources and tiles until the used database size,
@@ -813,7 +793,7 @@ bool OfflineDatabase::evict(uint64_t neededFreeSize) {
// size, and because pages can get fragmented on the database.
while (usedSize() + neededFreeSize + pageSize > maximumCacheSize) {
// clang-format off
- Statement accessedStmt = getStatement(
+ mapbox::sqlite::Query accessedQuery{ getStatement(
"SELECT max(accessed) "
"FROM ( "
" SELECT accessed "
@@ -829,16 +809,16 @@ bool OfflineDatabase::evict(uint64_t neededFreeSize) {
" WHERE tile_id IS NULL "
" ORDER BY accessed ASC LIMIT ?1 "
") "
- );
- accessedStmt->bind(1, 50);
+ ) };
+ accessedQuery.bind(1, 50);
// clang-format on
- if (!accessedStmt->run()) {
+ if (!accessedQuery.run()) {
return false;
}
- Timestamp accessed = accessedStmt->get<Timestamp>(0);
+ Timestamp accessed = accessedQuery.get<Timestamp>(0);
// clang-format off
- Statement stmt1 = getStatement(
+ mapbox::sqlite::Query resourceQuery{ getStatement(
"DELETE FROM resources "
"WHERE id IN ( "
" SELECT id FROM resources "
@@ -846,14 +826,14 @@ bool OfflineDatabase::evict(uint64_t neededFreeSize) {
" ON resource_id = resources.id "
" WHERE resource_id IS NULL "
" AND accessed <= ?1 "
- ") ");
+ ") ") };
// clang-format on
- stmt1->bind(1, accessed);
- stmt1->run();
- uint64_t changes1 = stmt1->changes();
+ resourceQuery.bind(1, accessed);
+ resourceQuery.run();
+ const uint64_t resourceChanges = resourceQuery.changes();
// clang-format off
- Statement stmt2 = getStatement(
+ mapbox::sqlite::Query tileQuery{ getStatement(
"DELETE FROM tiles "
"WHERE id IN ( "
" SELECT id FROM tiles "
@@ -861,16 +841,16 @@ bool OfflineDatabase::evict(uint64_t neededFreeSize) {
" ON tile_id = tiles.id "
" WHERE tile_id IS NULL "
" AND accessed <= ?1 "
- ") ");
+ ") ") };
// clang-format on
- stmt2->bind(1, accessed);
- stmt2->run();
- uint64_t changes2 = stmt2->changes();
+ tileQuery.bind(1, accessed);
+ tileQuery.run();
+ const uint64_t tileChanges = tileQuery.changes();
// The cached value of offlineTileCount does not need to be updated
// here because only non-offline tiles can be removed by eviction.
- if (changes1 == 0 && changes2 == 0) {
+ if (resourceChanges == 0 && tileChanges == 0) {
return false;
}
}
@@ -901,16 +881,16 @@ uint64_t OfflineDatabase::getOfflineMapboxTileCount() {
}
// clang-format off
- Statement stmt = getStatement(
+ mapbox::sqlite::Query query{ getStatement(
"SELECT COUNT(DISTINCT id) "
"FROM region_tiles, tiles "
"WHERE tile_id = tiles.id "
- "AND url_template LIKE 'mapbox://%' ");
+ "AND url_template LIKE 'mapbox://%' ") };
// clang-format on
- stmt->run();
+ query.run();
- offlineMapboxTileCount = stmt->get<int64_t>(0);
+ offlineMapboxTileCount = query.get<int64_t>(0);
return *offlineMapboxTileCount;
}
diff --git a/platform/default/mbgl/storage/offline_database.hpp b/platform/default/mbgl/storage/offline_database.hpp
index 91b544a9e0..9673ad8212 100644
--- a/platform/default/mbgl/storage/offline_database.hpp
+++ b/platform/default/mbgl/storage/offline_database.hpp
@@ -15,6 +15,7 @@ namespace mapbox {
namespace sqlite {
class Database;
class Statement;
+class Query;
} // namespace sqlite
} // namespace mapbox
@@ -66,20 +67,7 @@ private:
void migrateToVersion5();
void migrateToVersion6();
- class Statement {
- public:
- explicit Statement(mapbox::sqlite::Statement& stmt_) : stmt(stmt_) {}
- Statement(Statement&&) = default;
- Statement(const Statement&) = delete;
- ~Statement();
-
- mapbox::sqlite::Statement* operator->() { return &stmt; };
-
- private:
- mapbox::sqlite::Statement& stmt;
- };
-
- Statement getStatement(const char *);
+ mapbox::sqlite::Statement& getStatement(const char *);
optional<std::pair<Response, uint64_t>> getTile(const Resource::TileData&);
optional<int64_t> hasTile(const Resource::TileData&);
@@ -102,8 +90,8 @@ private:
std::pair<int64_t, int64_t> getCompletedTileCountAndSize(int64_t regionID);
const std::string path;
- std::unique_ptr<::mapbox::sqlite::Database> db;
- std::unordered_map<const char *, std::unique_ptr<::mapbox::sqlite::Statement>> statements;
+ std::unique_ptr<mapbox::sqlite::Database> db;
+ std::unordered_map<const char *, const std::unique_ptr<mapbox::sqlite::Statement>> statements;
template <class T>
T getPragma(const char *);
diff --git a/platform/default/run_loop.cpp b/platform/default/run_loop.cpp
index 5bccd21d56..868ee72114 100644
--- a/platform/default/run_loop.cpp
+++ b/platform/default/run_loop.cpp
@@ -129,11 +129,8 @@ LOOP_HANDLE RunLoop::getLoopHandle() {
return Get()->impl->loop;
}
-void RunLoop::push(std::shared_ptr<WorkTask> task) {
- withMutex([&] {
- queue.push(std::move(task));
- impl->async->send();
- });
+void RunLoop::wake() {
+ impl->async->send();
}
void RunLoop::run() {
diff --git a/platform/default/sqlite3.cpp b/platform/default/sqlite3.cpp
index 2e08354fdf..8a567d602e 100644
--- a/platform/default/sqlite3.cpp
+++ b/platform/default/sqlite3.cpp
@@ -69,14 +69,84 @@ public:
template <typename T>
using optional = std::experimental::optional<T>;
+static const char* codeToString(const int err) {
+ switch (err) {
+ case SQLITE_OK: return "SQLITE_OK";
+ case SQLITE_ERROR: return "SQLITE_ERROR";
+ case SQLITE_INTERNAL: return "SQLITE_INTERNAL";
+ case SQLITE_PERM: return "SQLITE_PERM";
+ case SQLITE_ABORT: return "SQLITE_ABORT";
+ case SQLITE_BUSY: return "SQLITE_BUSY";
+ case SQLITE_LOCKED: return "SQLITE_LOCKED";
+ case SQLITE_NOMEM: return "SQLITE_NOMEM";
+ case SQLITE_READONLY: return "SQLITE_READONLY";
+ case SQLITE_INTERRUPT: return "SQLITE_INTERRUPT";
+ case SQLITE_IOERR: return "SQLITE_IOERR";
+ case SQLITE_CORRUPT: return "SQLITE_CORRUPT";
+ case SQLITE_NOTFOUND: return "SQLITE_NOTFOUND";
+ case SQLITE_FULL: return "SQLITE_FULL";
+ case SQLITE_CANTOPEN: return "SQLITE_CANTOPEN";
+ case SQLITE_PROTOCOL: return "SQLITE_PROTOCOL";
+ case SQLITE_EMPTY: return "SQLITE_EMPTY";
+ case SQLITE_SCHEMA: return "SQLITE_SCHEMA";
+ case SQLITE_TOOBIG: return "SQLITE_TOOBIG";
+ case SQLITE_CONSTRAINT: return "SQLITE_CONSTRAINT";
+ case SQLITE_MISMATCH: return "SQLITE_MISMATCH";
+ case SQLITE_MISUSE: return "SQLITE_MISUSE";
+ case SQLITE_NOLFS: return "SQLITE_NOLFS";
+ case SQLITE_AUTH: return "SQLITE_AUTH";
+ case SQLITE_FORMAT: return "SQLITE_FORMAT";
+ case SQLITE_RANGE: return "SQLITE_RANGE";
+ case SQLITE_NOTADB: return "SQLITE_NOTADB";
+ case SQLITE_NOTICE: return "SQLITE_NOTICE";
+ case SQLITE_WARNING: return "SQLITE_WARNING";
+ case SQLITE_ROW: return "SQLITE_ROW";
+ case SQLITE_DONE: return "SQLITE_DONE";
+ default: return "<unknown>";
+ }
+}
+
static void errorLogCallback(void *, const int err, const char *msg) {
- if (err == SQLITE_ERROR) {
- mbgl::Log::Error(mbgl::Event::Database, "%s (Code %i)", msg, err);
- } else if (err == SQLITE_WARNING) {
- mbgl::Log::Warning(mbgl::Event::Database, "%s (Code %i)", msg, err);
- } else {
- mbgl::Log::Info(mbgl::Event::Database, "%s (Code %i)", msg, err);
+ auto severity = mbgl::EventSeverity::Info;
+
+ switch (err) {
+ case SQLITE_ERROR: // Generic error
+ case SQLITE_INTERNAL: // Internal logic error in SQLite
+ case SQLITE_PERM: // Access permission denied
+ case SQLITE_ABORT: // Callback routine requested an abort
+ case SQLITE_BUSY: // The database file is locked
+ case SQLITE_LOCKED: // A table in the database is locked
+ case SQLITE_NOMEM: // A malloc() failed
+ case SQLITE_READONLY: // Attempt to write a readonly database
+ case SQLITE_INTERRUPT: // Operation terminated by sqlite3_interrupt(
+ case SQLITE_IOERR: // Some kind of disk I/O error occurred
+ case SQLITE_CORRUPT: // The database disk image is malformed
+ case SQLITE_NOTFOUND: // Unknown opcode in sqlite3_file_control()
+ case SQLITE_FULL: // Insertion failed because database is full
+ case SQLITE_CANTOPEN: // Unable to open the database file
+ case SQLITE_PROTOCOL: // Database lock protocol error
+ case SQLITE_EMPTY: // Internal use only
+ case SQLITE_SCHEMA: // The database schema changed
+ case SQLITE_TOOBIG: // String or BLOB exceeds size limit
+ case SQLITE_CONSTRAINT: // Abort due to constraint violation
+ case SQLITE_MISMATCH: // Data type mismatch
+ case SQLITE_MISUSE: // Library used incorrectly
+ case SQLITE_NOLFS: // Uses OS features not supported on host
+ case SQLITE_AUTH: // Authorization denied
+ case SQLITE_FORMAT: // Not used
+ case SQLITE_RANGE: // 2nd parameter to sqlite3_bind out of range
+ case SQLITE_NOTADB: // File opened that is not a database file
+ severity = mbgl::EventSeverity::Error;
+ break;
+ case SQLITE_WARNING: // Warnings from sqlite3_log()
+ severity = mbgl::EventSeverity::Warning;
+ break;
+ case SQLITE_NOTICE: // Notifications from sqlite3_log()
+ default:
+ break;
}
+
+ mbgl::Log::Record(severity, mbgl::Event::Database, "%s (%s)", msg, codeToString(err));
}
const static bool sqliteVersionCheck __attribute__((unused)) = []() {
@@ -131,85 +201,93 @@ void Database::exec(const std::string &sql) {
}
}
-Statement Database::prepare(const char *query) {
- assert(impl);
- return Statement(this, query);
+Statement::Statement(Database& db, const char* sql)
+ : impl(std::make_unique<StatementImpl>(db.impl->db, sql)) {
}
-Statement::Statement(Database *db, const char *sql)
- : impl(std::make_unique<StatementImpl>(db->impl->db, sql))
-{
+Statement::~Statement() {
+#ifndef NDEBUG
+ // Crash if we're destructing this object while we know a Query object references this.
+ assert(!used);
+#endif
}
-Statement::Statement(Statement &&other) {
- *this = std::move(other);
-}
+Query::Query(Statement& stmt_) : stmt(stmt_) {
+ assert(stmt.impl);
-Statement &Statement::operator=(Statement &&other) {
- std::swap(impl, other.impl);
- return *this;
+#ifndef NDEBUG
+ assert(!stmt.used);
+ stmt.used = true;
+#endif
}
-Statement::~Statement() = default;
+Query::~Query() {
+ reset();
+ clearBindings();
-template <> void Statement::bind(int offset, std::nullptr_t) {
- assert(impl);
- impl->check(sqlite3_bind_null(impl->stmt, offset));
+#ifndef NDEBUG
+ stmt.used = false;
+#endif
}
-template <> void Statement::bind(int offset, int8_t value) {
- assert(impl);
- impl->check(sqlite3_bind_int64(impl->stmt, offset, value));
+template <> void Query::bind(int offset, std::nullptr_t) {
+ assert(stmt.impl);
+ stmt.impl->check(sqlite3_bind_null(stmt.impl->stmt, offset));
}
-template <> void Statement::bind(int offset, int16_t value) {
- assert(impl);
- impl->check(sqlite3_bind_int64(impl->stmt, offset, value));
+template <> void Query::bind(int offset, int8_t value) {
+ assert(stmt.impl);
+ stmt.impl->check(sqlite3_bind_int64(stmt.impl->stmt, offset, value));
}
-template <> void Statement::bind(int offset, int32_t value) {
- assert(impl);
- impl->check(sqlite3_bind_int64(impl->stmt, offset, value));
+template <> void Query::bind(int offset, int16_t value) {
+ assert(stmt.impl);
+ stmt.impl->check(sqlite3_bind_int64(stmt.impl->stmt, offset, value));
}
-template <> void Statement::bind(int offset, int64_t value) {
- assert(impl);
- impl->check(sqlite3_bind_int64(impl->stmt, offset, value));
+template <> void Query::bind(int offset, int32_t value) {
+ assert(stmt.impl);
+ stmt.impl->check(sqlite3_bind_int64(stmt.impl->stmt, offset, value));
}
-template <> void Statement::bind(int offset, uint8_t value) {
- assert(impl);
- impl->check(sqlite3_bind_int64(impl->stmt, offset, value));
+template <> void Query::bind(int offset, int64_t value) {
+ assert(stmt.impl);
+ stmt.impl->check(sqlite3_bind_int64(stmt.impl->stmt, offset, value));
}
-template <> void Statement::bind(int offset, uint16_t value) {
- assert(impl);
- impl->check(sqlite3_bind_int64(impl->stmt, offset, value));
+template <> void Query::bind(int offset, uint8_t value) {
+ assert(stmt.impl);
+ stmt.impl->check(sqlite3_bind_int64(stmt.impl->stmt, offset, value));
}
-template <> void Statement::bind(int offset, uint32_t value) {
- assert(impl);
- impl->check(sqlite3_bind_int64(impl->stmt, offset, value));
+template <> void Query::bind(int offset, uint16_t value) {
+ assert(stmt.impl);
+ stmt.impl->check(sqlite3_bind_int64(stmt.impl->stmt, offset, value));
}
-template <> void Statement::bind(int offset, float value) {
- assert(impl);
- impl->check(sqlite3_bind_double(impl->stmt, offset, value));
+template <> void Query::bind(int offset, uint32_t value) {
+ assert(stmt.impl);
+ stmt.impl->check(sqlite3_bind_int64(stmt.impl->stmt, offset, value));
}
-template <> void Statement::bind(int offset, double value) {
- assert(impl);
- impl->check(sqlite3_bind_double(impl->stmt, offset, value));
+template <> void Query::bind(int offset, float value) {
+ assert(stmt.impl);
+ stmt.impl->check(sqlite3_bind_double(stmt.impl->stmt, offset, value));
}
-template <> void Statement::bind(int offset, bool value) {
- assert(impl);
- impl->check(sqlite3_bind_int(impl->stmt, offset, value));
+template <> void Query::bind(int offset, double value) {
+ assert(stmt.impl);
+ stmt.impl->check(sqlite3_bind_double(stmt.impl->stmt, offset, value));
}
-template <> void Statement::bind(int offset, const char *value) {
- assert(impl);
- impl->check(sqlite3_bind_text(impl->stmt, offset, value, -1, SQLITE_STATIC));
+template <> void Query::bind(int offset, bool value) {
+ assert(stmt.impl);
+ stmt.impl->check(sqlite3_bind_int(stmt.impl->stmt, offset, value));
+}
+
+template <> void Query::bind(int offset, const char *value) {
+ assert(stmt.impl);
+ stmt.impl->check(sqlite3_bind_text(stmt.impl->stmt, offset, value, -1, SQLITE_STATIC));
}
// We currently cannot use sqlite3_bind_blob64 / sqlite3_bind_text64 because they
@@ -219,40 +297,40 @@ template <> void Statement::bind(int offset, const char *value) {
// According to http://stackoverflow.com/questions/14288128/what-version-of-sqlite-does-ios-provide,
// the first iOS version with 3.8.7+ was 9.0, with 3.8.8.
-void Statement::bind(int offset, const char * value, std::size_t length, bool retain) {
- assert(impl);
+void Query::bind(int offset, const char * value, std::size_t length, bool retain) {
+ assert(stmt.impl);
if (length > std::numeric_limits<int>::max()) {
throw std::range_error("value too long for sqlite3_bind_text");
}
- impl->check(sqlite3_bind_text(impl->stmt, offset, value, int(length),
+ stmt.impl->check(sqlite3_bind_text(stmt.impl->stmt, offset, value, int(length),
retain ? SQLITE_TRANSIENT : SQLITE_STATIC));
}
-void Statement::bind(int offset, const std::string& value, bool retain) {
+void Query::bind(int offset, const std::string& value, bool retain) {
bind(offset, value.data(), value.size(), retain);
}
-void Statement::bindBlob(int offset, const void * value, std::size_t length, bool retain) {
- assert(impl);
+void Query::bindBlob(int offset, const void * value, std::size_t length, bool retain) {
+ assert(stmt.impl);
if (length > std::numeric_limits<int>::max()) {
throw std::range_error("value too long for sqlite3_bind_text");
}
- impl->check(sqlite3_bind_blob(impl->stmt, offset, value, int(length),
+ stmt.impl->check(sqlite3_bind_blob(stmt.impl->stmt, offset, value, int(length),
retain ? SQLITE_TRANSIENT : SQLITE_STATIC));
}
-void Statement::bindBlob(int offset, const std::vector<uint8_t>& value, bool retain) {
+void Query::bindBlob(int offset, const std::vector<uint8_t>& value, bool retain) {
bindBlob(offset, value.data(), value.size(), retain);
}
template <>
-void Statement::bind(
+void Query::bind(
int offset, std::chrono::time_point<std::chrono::system_clock, std::chrono::seconds> value) {
- assert(impl);
- impl->check(sqlite3_bind_int64(impl->stmt, offset, std::chrono::system_clock::to_time_t(value)));
+ assert(stmt.impl);
+ stmt.impl->check(sqlite3_bind_int64(stmt.impl->stmt, offset, std::chrono::system_clock::to_time_t(value)));
}
-template <> void Statement::bind(int offset, optional<std::string> value) {
+template <> void Query::bind(int offset, optional<std::string> value) {
if (!value) {
bind(offset, nullptr);
} else {
@@ -261,7 +339,7 @@ template <> void Statement::bind(int offset, optional<std::string> value) {
}
template <>
-void Statement::bind(
+void Query::bind(
int offset,
optional<std::chrono::time_point<std::chrono::system_clock, std::chrono::seconds>> value) {
if (!value) {
@@ -271,86 +349,86 @@ void Statement::bind(
}
}
-bool Statement::run() {
- assert(impl);
- const int err = sqlite3_step(impl->stmt);
- impl->lastInsertRowId = sqlite3_last_insert_rowid(sqlite3_db_handle(impl->stmt));
- impl->changes = sqlite3_changes(sqlite3_db_handle(impl->stmt));
+bool Query::run() {
+ assert(stmt.impl);
+ const int err = sqlite3_step(stmt.impl->stmt);
+ stmt.impl->lastInsertRowId = sqlite3_last_insert_rowid(sqlite3_db_handle(stmt.impl->stmt));
+ stmt.impl->changes = sqlite3_changes(sqlite3_db_handle(stmt.impl->stmt));
if (err == SQLITE_DONE) {
return false;
} else if (err == SQLITE_ROW) {
return true;
} else if (err != SQLITE_OK) {
- throw Exception { err, sqlite3_errmsg(sqlite3_db_handle(impl->stmt)) };
+ throw Exception { err, sqlite3_errmsg(sqlite3_db_handle(stmt.impl->stmt)) };
} else {
return false;
}
}
-template <> bool Statement::get(int offset) {
- assert(impl);
- return sqlite3_column_int(impl->stmt, offset);
+template <> bool Query::get(int offset) {
+ assert(stmt.impl);
+ return sqlite3_column_int(stmt.impl->stmt, offset);
}
-template <> int Statement::get(int offset) {
- assert(impl);
- return sqlite3_column_int(impl->stmt, offset);
+template <> int Query::get(int offset) {
+ assert(stmt.impl);
+ return sqlite3_column_int(stmt.impl->stmt, offset);
}
-template <> int64_t Statement::get(int offset) {
- assert(impl);
- return sqlite3_column_int64(impl->stmt, offset);
+template <> int64_t Query::get(int offset) {
+ assert(stmt.impl);
+ return sqlite3_column_int64(stmt.impl->stmt, offset);
}
-template <> double Statement::get(int offset) {
- assert(impl);
- return sqlite3_column_double(impl->stmt, offset);
+template <> double Query::get(int offset) {
+ assert(stmt.impl);
+ return sqlite3_column_double(stmt.impl->stmt, offset);
}
-template <> std::string Statement::get(int offset) {
- assert(impl);
+template <> std::string Query::get(int offset) {
+ assert(stmt.impl);
return {
- reinterpret_cast<const char *>(sqlite3_column_blob(impl->stmt, offset)),
- size_t(sqlite3_column_bytes(impl->stmt, offset))
+ reinterpret_cast<const char *>(sqlite3_column_blob(stmt.impl->stmt, offset)),
+ size_t(sqlite3_column_bytes(stmt.impl->stmt, offset))
};
}
-template <> std::vector<uint8_t> Statement::get(int offset) {
- assert(impl);
- const auto* begin = reinterpret_cast<const uint8_t*>(sqlite3_column_blob(impl->stmt, offset));
- const uint8_t* end = begin + sqlite3_column_bytes(impl->stmt, offset);
+template <> std::vector<uint8_t> Query::get(int offset) {
+ assert(stmt.impl);
+ const auto* begin = reinterpret_cast<const uint8_t*>(sqlite3_column_blob(stmt.impl->stmt, offset));
+ const uint8_t* end = begin + sqlite3_column_bytes(stmt.impl->stmt, offset);
return { begin, end };
}
template <>
std::chrono::time_point<std::chrono::system_clock, std::chrono::seconds>
-Statement::get(int offset) {
- assert(impl);
+Query::get(int offset) {
+ assert(stmt.impl);
return std::chrono::time_point_cast<std::chrono::seconds>(
- std::chrono::system_clock::from_time_t(sqlite3_column_int64(impl->stmt, offset)));
+ std::chrono::system_clock::from_time_t(sqlite3_column_int64(stmt.impl->stmt, offset)));
}
-template <> optional<int64_t> Statement::get(int offset) {
- assert(impl);
- if (sqlite3_column_type(impl->stmt, offset) == SQLITE_NULL) {
+template <> optional<int64_t> Query::get(int offset) {
+ assert(stmt.impl);
+ if (sqlite3_column_type(stmt.impl->stmt, offset) == SQLITE_NULL) {
return optional<int64_t>();
} else {
return get<int64_t>(offset);
}
}
-template <> optional<double> Statement::get(int offset) {
- assert(impl);
- if (sqlite3_column_type(impl->stmt, offset) == SQLITE_NULL) {
+template <> optional<double> Query::get(int offset) {
+ assert(stmt.impl);
+ if (sqlite3_column_type(stmt.impl->stmt, offset) == SQLITE_NULL) {
return optional<double>();
} else {
return get<double>(offset);
}
}
-template <> optional<std::string> Statement::get(int offset) {
- assert(impl);
- if (sqlite3_column_type(impl->stmt, offset) == SQLITE_NULL) {
+template <> optional<std::string> Query::get(int offset) {
+ assert(stmt.impl);
+ if (sqlite3_column_type(stmt.impl->stmt, offset) == SQLITE_NULL) {
return optional<std::string>();
} else {
return get<std::string>(offset);
@@ -359,9 +437,9 @@ template <> optional<std::string> Statement::get(int offset) {
template <>
optional<std::chrono::time_point<std::chrono::system_clock, std::chrono::seconds>>
-Statement::get(int offset) {
- assert(impl);
- if (sqlite3_column_type(impl->stmt, offset) == SQLITE_NULL) {
+Query::get(int offset) {
+ assert(stmt.impl);
+ if (sqlite3_column_type(stmt.impl->stmt, offset) == SQLITE_NULL) {
return {};
} else {
return get<std::chrono::time_point<std::chrono::system_clock, std::chrono::seconds>>(
@@ -369,24 +447,24 @@ Statement::get(int offset) {
}
}
-void Statement::reset() {
- assert(impl);
- sqlite3_reset(impl->stmt);
+void Query::reset() {
+ assert(stmt.impl);
+ sqlite3_reset(stmt.impl->stmt);
}
-void Statement::clearBindings() {
- assert(impl);
- sqlite3_clear_bindings(impl->stmt);
+void Query::clearBindings() {
+ assert(stmt.impl);
+ sqlite3_clear_bindings(stmt.impl->stmt);
}
-int64_t Statement::lastInsertRowId() const {
- assert(impl);
- return impl->lastInsertRowId;
+int64_t Query::lastInsertRowId() const {
+ assert(stmt.impl);
+ return stmt.impl->lastInsertRowId;
}
-uint64_t Statement::changes() const {
- assert(impl);
- auto changes_ = impl->changes;
+uint64_t Query::changes() const {
+ assert(stmt.impl);
+ auto changes_ = stmt.impl->changes;
return (changes_ < 0 ? 0 : changes_);
}
diff --git a/platform/default/sqlite3.hpp b/platform/default/sqlite3.hpp
index 82e3ceff6d..20d09b550c 100644
--- a/platform/default/sqlite3.hpp
+++ b/platform/default/sqlite3.hpp
@@ -19,21 +19,55 @@ enum OpenFlag : int {
PrivateCache = 0x00040000,
};
-struct Exception : std::runtime_error {
- enum Code : int {
- OK = 0,
- CANTOPEN = 14,
- NOTADB = 26
- };
+enum class ResultCode : int {
+ OK = 0,
+ Error = 1,
+ Internal = 2,
+ Perm = 3,
+ Abort = 4,
+ Busy = 5,
+ Locked = 6,
+ NoMem = 7,
+ ReadOnly = 8,
+ Interrupt = 9,
+ IOErr = 10,
+ Corrupt = 11,
+ NotFound = 12,
+ Full = 13,
+ CantOpen = 14,
+ Protocol = 15,
+ Schema = 17,
+ TooBig = 18,
+ Constraint = 19,
+ Mismatch = 20,
+ Misuse = 21,
+ NoLFS = 22,
+ Auth = 23,
+ Range = 25,
+ NotADB = 26
+};
- Exception(int err, const char *msg) : std::runtime_error(msg), code(err) {}
- Exception(int err, const std::string& msg) : std::runtime_error(msg), code(err) {}
- const int code = OK;
+class Exception : public std::runtime_error {
+public:
+ Exception(int err, const char* msg)
+ : std::runtime_error(msg), code(static_cast<ResultCode>(err)) {
+ }
+ Exception(ResultCode err, const char* msg)
+ : std::runtime_error(msg), code(err) {
+ }
+ Exception(int err, const std::string& msg)
+ : std::runtime_error(msg), code(static_cast<ResultCode>(err)) {
+ }
+ Exception(ResultCode err, const std::string& msg)
+ : std::runtime_error(msg), code(err) {
+ }
+ const ResultCode code = ResultCode::OK;
};
class DatabaseImpl;
class Statement;
class StatementImpl;
+class Query;
class Database {
private:
@@ -48,7 +82,6 @@ public:
void setBusyTimeout(std::chrono::milliseconds);
void exec(const std::string &sql);
- Statement prepare(const char *query);
private:
std::unique_ptr<DatabaseImpl> impl;
@@ -56,28 +89,54 @@ private:
friend class Statement;
};
+// A Statement object represents a prepared statement that can be run repeatedly run with a Query object.
class Statement {
+public:
+ Statement(Database& db, const char* sql);
+ Statement(const Statement&) = delete;
+ Statement(Statement&&) = delete;
+ Statement& operator=(const Statement&) = delete;
+ Statement& operator=(Statement&&) = delete;
+ ~Statement();
+
+ friend class Query;
+
private:
- Statement(const Statement &) = delete;
- Statement &operator=(const Statement &) = delete;
+ std::unique_ptr<StatementImpl> impl;
+
+#ifndef NDEBUG
+ // This flag stores whether there exists a Query object that uses this prepared statement.
+ // There may only be one Query object at a time. Statement objects must outlive Query objects.
+ // While a Query object exists, a Statement object may not be moved or deleted.
+ bool used = false;
+#endif
+};
+// A Query object is used to run a database query with a prepared statement (stored in a Statement
+// object). There may only exist one Query object per Statement object. Query objects are designed
+// to be constructed and destroyed frequently.
+class Query {
public:
- Statement(Database *db, const char *sql);
- Statement(Statement &&);
- ~Statement();
- Statement &operator=(Statement &&);
+ Query(Statement&);
+ Query(const Query&) = delete;
+ Query(Query&&) = delete;
+ Query& operator=(const Query&) = delete;
+ Query& operator=(Query&&) = delete;
+ ~Query();
- template <typename T> void bind(int offset, T value);
+ template <typename T>
+ void bind(int offset, T value);
// Text
- void bind(int offset, const char *, std::size_t length, bool retain = true);
+ void bind(int offset, const char*, std::size_t length, bool retain = true);
void bind(int offset, const std::string&, bool retain = true);
// Blob
- void bindBlob(int offset, const void *, std::size_t length, bool retain = true);
+ void bindBlob(int offset, const void*, std::size_t length, bool retain = true);
void bindBlob(int offset, const std::vector<uint8_t>&, bool retain = true);
- template <typename T> T get(int offset);
+ template <typename T>
+ T get(int offset);
bool run();
void reset();
@@ -87,7 +146,7 @@ public:
uint64_t changes() const;
private:
- std::unique_ptr<StatementImpl> impl;
+ Statement& stmt;
};
class Transaction {
diff --git a/platform/ios/CHANGELOG.md b/platform/ios/CHANGELOG.md
index 75a77720ae..471dd949c1 100644
--- a/platform/ios/CHANGELOG.md
+++ b/platform/ios/CHANGELOG.md
@@ -10,6 +10,7 @@ Mapbox welcomes participation and contributions from everyone. Please read [CONT
### Styles and rendering
+* Added support for a new layer type: `MGLHeatmapStyleLayer`, a powerful way to visualize point data distributions using heatmaps, fully customizable through runtime styling. [#11046](https://github.com/mapbox/mapbox-gl-native/pull/11046)
* The layout and paint properties on subclasses of `MGLStyleLayer` are now of type `NSExpression` instead of `MGLStyleValue`. A new “Predicates and Expressions” guide provides an overview of the supported operators. ([#10726](https://github.com/mapbox/mapbox-gl-native/pull/10726))
* Added an `MGLComputedShapeSource` class that allows applications to supply vector data to a style layer on a per-tile basis. ([#9983](https://github.com/mapbox/mapbox-gl-native/pull/9983))
* A style can now display smooth hillshading and customize its appearance at runtime using the `MGLHillshadeStyleLayer` class. Hillshading is based on a rasterized digital elevation model supplied by the `MGLRasterDEMSource` class. ([#10642](https://github.com/mapbox/mapbox-gl-native/pull/10642))
@@ -32,7 +33,16 @@ Mapbox welcomes participation and contributions from everyone. Please read [CONT
* Added a Hebrew localization. ([#10967](https://github.com/mapbox/mapbox-gl-native/pull/10967))
* Long-pressing the attribution button causes the SDK’s version number to be displayed in the action sheet that appears. ([#10650](https://github.com/mapbox/mapbox-gl-native/pull/10650))
-## 3.7.3
+## 3.7.5 - February 16, 2018
+
+* Fixed an issue where requesting location services permission would trigger an unrecoverable loop. ([#11229](https://github.com/mapbox/mapbox-gl-native/pull/11229))
+
+## 3.7.4 - February 12, 2018
+
+* Added the `MGLTileSourceOptionTileCoordinateBounds` option to create an `MGLTileSource` that only supplies tiles within a specific geographic bounding box. ([#11141](https://github.com/mapbox/mapbox-gl-native/pull/11141))
+* Fixed an issue that caused `-[MGLMapSnapshotter pointForCoordinate:]` to return the wrong point. ([#11035](https://github.com/mapbox/mapbox-gl-native/pull/11035))
+
+## 3.7.3 - January 10, 2018
* Fixed a crash while zooming while annotations are present on the map. ([#10791](https://github.com/mapbox/mapbox-gl-native/pull/10791))
* CJK characters can be displayed in a locally installed font or a custom font bundled with the application, reducing map download times. Specify the font name using the `MGLIdeographicFontFamilyName` key in the application’s Info.plist file. ([#10522](https://github.com/mapbox/mapbox-gl-native/pull/10522))
diff --git a/platform/ios/Mapbox-iOS-SDK-nightly-dynamic.podspec b/platform/ios/Mapbox-iOS-SDK-nightly-dynamic.podspec
index fb9511e707..bf2f854b50 100644
--- a/platform/ios/Mapbox-iOS-SDK-nightly-dynamic.podspec
+++ b/platform/ios/Mapbox-iOS-SDK-nightly-dynamic.podspec
@@ -1,6 +1,6 @@
Pod::Spec.new do |m|
- version = '3.7.3'
+ version = '3.7.5'
m.name = 'Mapbox-iOS-SDK-nightly-dynamic'
m.version = "#{version}-nightly"
diff --git a/platform/ios/Mapbox-iOS-SDK-symbols.podspec b/platform/ios/Mapbox-iOS-SDK-symbols.podspec
index ea8ae667f6..e0c787e5da 100644
--- a/platform/ios/Mapbox-iOS-SDK-symbols.podspec
+++ b/platform/ios/Mapbox-iOS-SDK-symbols.podspec
@@ -1,6 +1,6 @@
Pod::Spec.new do |m|
- version = '3.7.3'
+ version = '3.7.5'
m.name = 'Mapbox-iOS-SDK-symbols'
m.version = "#{version}-symbols"
diff --git a/platform/ios/Mapbox-iOS-SDK.podspec b/platform/ios/Mapbox-iOS-SDK.podspec
index 1e81272d6d..ea7ce47a8b 100644
--- a/platform/ios/Mapbox-iOS-SDK.podspec
+++ b/platform/ios/Mapbox-iOS-SDK.podspec
@@ -1,6 +1,6 @@
Pod::Spec.new do |m|
- version = '3.7.3'
+ version = '3.7.5'
m.name = 'Mapbox-iOS-SDK'
m.version = version
diff --git a/platform/ios/app/MBXSnapshotsViewController.m b/platform/ios/app/MBXSnapshotsViewController.m
index 3bf93d8721..95d3251e2e 100644
--- a/platform/ios/app/MBXSnapshotsViewController.m
+++ b/platform/ios/app/MBXSnapshotsViewController.m
@@ -50,12 +50,13 @@
options.zoomLevel = 10;
// Create and start the snapshotter
+ __weak UIImageView *weakImageView = imageView;
MGLMapSnapshotter* snapshotter = [[MGLMapSnapshotter alloc] initWithOptions:options];
[snapshotter startWithCompletionHandler: ^(MGLMapSnapshot* snapshot, NSError *error) {
if (error) {
NSLog(@"Could not load snapshot: %@", [error localizedDescription]);
} else {
- imageView.image = snapshot.image;
+ weakImageView.image = snapshot.image;
}
}];
diff --git a/platform/ios/app/MBXViewController.m b/platform/ios/app/MBXViewController.m
index 0f617188b9..282dc901d6 100644
--- a/platform/ios/app/MBXViewController.m
+++ b/platform/ios/app/MBXViewController.m
@@ -1280,6 +1280,7 @@ typedef NS_ENUM(NSInteger, MBXSettingsMiscellaneousRows) {
- (void)styleDynamicPointCollection
{
[self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(36.9979, -109.0441) zoomLevel:14 animated:NO];
+
CLLocationCoordinate2D coordinates[] = {
{37.00145594210082, -109.04960632324219},
{37.00173012609867, -109.0404224395752},
@@ -1889,7 +1890,8 @@ typedef NS_ENUM(NSInteger, MBXSettingsMiscellaneousRows) {
[self updateHUD];
}
-- (void)mapView:(MGLMapView *)mapView regionDidChangeAnimated:(BOOL)animated {
+- (void)mapView:(MGLMapView *)mapView regionDidChangeWithReason:(MGLCameraChangeReason)reason animated:(BOOL)animated
+{
[self updateHUD];
}
diff --git a/platform/ios/docs/guides/For Style Authors.md b/platform/ios/docs/guides/For Style Authors.md
index 51cd87a766..c4aabb0410 100644
--- a/platform/ios/docs/guides/For Style Authors.md
+++ b/platform/ios/docs/guides/For Style Authors.md
@@ -109,6 +109,7 @@ the following terms for concepts defined in the style specification:
In the style specification | In the SDK
---------------------------|---------
+bounds | coordinate bounds
filter | predicate
function type | interpolation mode
id | identifier
@@ -149,9 +150,11 @@ In style JSON | In TileJSON | In the SDK
`tiles` | `tiles` | `tileURLTemplates` parameter in `-[MGLTileSource initWithIdentifier:tileURLTemplates:options:]`
`minzoom` | `minzoom` | `MGLTileSourceOptionMinimumZoomLevel`
`maxzoom` | `maxzoom` | `MGLTileSourceOptionMaximumZoomLevel`
+`bounds` | `bounds` | `MGLTileSourceOptionCoordinateBounds`
`tileSize` | — | `MGLTileSourceOptionTileSize`
`attribution` | `attribution` | `MGLTileSourceOptionAttributionHTMLString` (but consider specifying `MGLTileSourceOptionAttributionInfos` instead for improved security)
`scheme` | `scheme` | `MGLTileSourceOptionTileCoordinateSystem`
+`encoding` | – | `MGLTileSourceOptionDEMEncoding`
### Shape sources
@@ -190,6 +193,7 @@ In style JSON | In the SDK
`circle` | `MGLCircleStyleLayer`
`fill` | `MGLFillStyleLayer`
`fill-extrusion` | `MGLFillExtrusionStyleLayer`
+`heatmap` | `MGLHeatmapStyleLayer`
`hillshade` | `MGLHillshadeStyleLayer`
`line` | `MGLLineStyleLayer`
`raster` | `MGLRasterStyleLayer`
diff --git a/platform/ios/ios.xcodeproj/project.pbxproj b/platform/ios/ios.xcodeproj/project.pbxproj
index d44fee3430..07fae5945c 100644
--- a/platform/ios/ios.xcodeproj/project.pbxproj
+++ b/platform/ios/ios.xcodeproj/project.pbxproj
@@ -28,6 +28,8 @@
16376B471FFDB92B0000563E /* one-liner.json in Resources */ = {isa = PBXBuildFile; fileRef = DA35D0871E1A6309007DED41 /* one-liner.json */; };
16376B491FFEED010000563E /* MGLMapViewLayoutTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 16376B481FFEED010000563E /* MGLMapViewLayoutTests.m */; };
165D0CE720005419009A3C66 /* Mapbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA8847D21CBAF91600AB86E3 /* Mapbox.framework */; };
+ 170C437C2029D96F00863DF0 /* MGLHeatmapColorTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 170C43782028D49800863DF0 /* MGLHeatmapColorTests.mm */; };
+ 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 */; };
1F06668A1EC64F8E001C16D7 /* MGLLight.h in Headers */ = {isa = PBXBuildFile; fileRef = 1F0666881EC64F8E001C16D7 /* MGLLight.h */; settings = {ATTRIBUTES = (Public, ); }; };
@@ -226,6 +228,10 @@
55E2AD131E5B125400E8C587 /* MGLOfflineStorageTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 55E2AD121E5B125400E8C587 /* MGLOfflineStorageTests.mm */; };
632281DF1E6F855900D75A5D /* MBXEmbeddedMapViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 632281DE1E6F855900D75A5D /* MBXEmbeddedMapViewController.m */; };
6407D6701E0085FD00F6A9C3 /* MGLDocumentationExampleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6407D66F1E0085FD00F6A9C3 /* MGLDocumentationExampleTests.swift */; };
+ 8989B17C201A48EB0081CF59 /* MGLHeatmapStyleLayer.h in Headers */ = {isa = PBXBuildFile; fileRef = 8989B17A201A48EA0081CF59 /* MGLHeatmapStyleLayer.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 8989B17D201A48EB0081CF59 /* MGLHeatmapStyleLayer.h in Headers */ = {isa = PBXBuildFile; fileRef = 8989B17A201A48EA0081CF59 /* MGLHeatmapStyleLayer.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 8989B17E201A48EB0081CF59 /* MGLHeatmapStyleLayer.mm in Sources */ = {isa = PBXBuildFile; fileRef = 8989B17B201A48EA0081CF59 /* MGLHeatmapStyleLayer.mm */; };
+ 8989B17F201A48EB0081CF59 /* MGLHeatmapStyleLayer.mm in Sources */ = {isa = PBXBuildFile; fileRef = 8989B17B201A48EA0081CF59 /* MGLHeatmapStyleLayer.mm */; };
920A3E5D1E6F995200C16EFC /* MGLSourceQueryTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 920A3E5C1E6F995200C16EFC /* MGLSourceQueryTests.m */; };
927FBCFC1F4DAA8300F8BF1F /* MBXSnapshotsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 927FBCFB1F4DAA8300F8BF1F /* MBXSnapshotsViewController.m */; };
927FBCFF1F4DB05500F8BF1F /* MGLMapSnapshotter.h in Headers */ = {isa = PBXBuildFile; fileRef = 927FBCFD1F4DB05500F8BF1F /* MGLMapSnapshotter.h */; settings = {ATTRIBUTES = (Public, ); }; };
@@ -297,6 +303,12 @@
96E5170320005A6800A02306 /* FABKitProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = DA8848811CBB033F00AB86E3 /* FABKitProtocol.h */; };
96E5170420005A6B00A02306 /* SMCalloutView.h in Headers */ = {isa = PBXBuildFile; fileRef = DA8848891CBB037E00AB86E3 /* SMCalloutView.h */; };
96F3F73C1F57124B003E2D2C /* MGLUserLocationHeadingIndicator.h in Headers */ = {isa = PBXBuildFile; fileRef = 96F3F73B1F5711F1003E2D2C /* MGLUserLocationHeadingIndicator.h */; };
+ AC518DFF201BB55A00EBC820 /* MGLTelemetryConfig.h in Headers */ = {isa = PBXBuildFile; fileRef = AC518DFD201BB55A00EBC820 /* MGLTelemetryConfig.h */; };
+ AC518E00201BB55A00EBC820 /* MGLTelemetryConfig.h in Headers */ = {isa = PBXBuildFile; fileRef = AC518DFD201BB55A00EBC820 /* MGLTelemetryConfig.h */; };
+ AC518E03201BB56000EBC820 /* MGLTelemetryConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = AC518DFE201BB55A00EBC820 /* MGLTelemetryConfig.m */; };
+ AC518E04201BB56100EBC820 /* MGLTelemetryConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = AC518DFE201BB55A00EBC820 /* MGLTelemetryConfig.m */; };
+ CA55CD41202C16AA00CE7095 /* MGLCameraChangeReason.h in Headers */ = {isa = PBXBuildFile; fileRef = CA55CD3E202C16AA00CE7095 /* MGLCameraChangeReason.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ CA55CD42202C16AA00CE7095 /* MGLCameraChangeReason.h in Headers */ = {isa = PBXBuildFile; fileRef = CA55CD3E202C16AA00CE7095 /* MGLCameraChangeReason.h */; settings = {ATTRIBUTES = (Public, ); }; };
DA00FC8E1D5EEB0D009AABC8 /* MGLAttributionInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = DA00FC8C1D5EEB0D009AABC8 /* MGLAttributionInfo.h */; settings = {ATTRIBUTES = (Public, ); }; };
DA00FC8F1D5EEB0D009AABC8 /* MGLAttributionInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = DA00FC8C1D5EEB0D009AABC8 /* MGLAttributionInfo.h */; settings = {ATTRIBUTES = (Public, ); }; };
DA00FC901D5EEB0D009AABC8 /* MGLAttributionInfo.mm in Sources */ = {isa = PBXBuildFile; fileRef = DA00FC8D1D5EEB0D009AABC8 /* MGLAttributionInfo.mm */; };
@@ -664,6 +676,8 @@
16376B3F1FFDB4B40000563E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
16376B401FFDB4B40000563E /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
16376B481FFEED010000563E /* MGLMapViewLayoutTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MGLMapViewLayoutTests.m; sourceTree = "<group>"; };
+ 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>"; };
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>"; };
@@ -795,6 +809,8 @@
632281DD1E6F855900D75A5D /* MBXEmbeddedMapViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MBXEmbeddedMapViewController.h; sourceTree = "<group>"; };
632281DE1E6F855900D75A5D /* MBXEmbeddedMapViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MBXEmbeddedMapViewController.m; sourceTree = "<group>"; };
6407D66F1E0085FD00F6A9C3 /* MGLDocumentationExampleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MGLDocumentationExampleTests.swift; path = ../../darwin/test/MGLDocumentationExampleTests.swift; sourceTree = "<group>"; };
+ 8989B17A201A48EA0081CF59 /* MGLHeatmapStyleLayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLHeatmapStyleLayer.h; sourceTree = "<group>"; };
+ 8989B17B201A48EA0081CF59 /* MGLHeatmapStyleLayer.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLHeatmapStyleLayer.mm; sourceTree = "<group>"; };
920A3E5C1E6F995200C16EFC /* MGLSourceQueryTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MGLSourceQueryTests.m; path = ../../darwin/test/MGLSourceQueryTests.m; sourceTree = "<group>"; };
927FBCFA1F4DAA8300F8BF1F /* MBXSnapshotsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MBXSnapshotsViewController.h; sourceTree = "<group>"; };
927FBCFB1F4DAA8300F8BF1F /* MBXSnapshotsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MBXSnapshotsViewController.m; sourceTree = "<group>"; };
@@ -829,6 +845,9 @@
96E0272D1E57C7E6004B8E66 /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = vi.lproj/Localizable.strings; sourceTree = "<group>"; };
96E0272E1E57C7E7004B8E66 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Localizable.strings"; sourceTree = "<group>"; };
96F3F73B1F5711F1003E2D2C /* MGLUserLocationHeadingIndicator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MGLUserLocationHeadingIndicator.h; sourceTree = "<group>"; };
+ AC518DFD201BB55A00EBC820 /* MGLTelemetryConfig.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MGLTelemetryConfig.h; sourceTree = "<group>"; };
+ AC518DFE201BB55A00EBC820 /* MGLTelemetryConfig.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MGLTelemetryConfig.m; sourceTree = "<group>"; };
+ CA55CD3E202C16AA00CE7095 /* MGLCameraChangeReason.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLCameraChangeReason.h; 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>"; };
DA0CD58F1CF56F6A00A5F5A5 /* MGLFeatureTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLFeatureTests.mm; path = ../../darwin/test/MGLFeatureTests.mm; sourceTree = "<group>"; };
@@ -1227,6 +1246,8 @@
FA68F1481E9D656600F9F6C2 /* MGLFillExtrusionStyleLayer.h */,
FA68F1491E9D656600F9F6C2 /* MGLFillExtrusionStyleLayer.mm */,
35D13AC11D3D19DD00AFB4E0 /* MGLFillStyleLayer.h */,
+ 8989B17A201A48EA0081CF59 /* MGLHeatmapStyleLayer.h */,
+ 8989B17B201A48EA0081CF59 /* MGLHeatmapStyleLayer.mm */,
35D13AC21D3D19DD00AFB4E0 /* MGLFillStyleLayer.mm */,
3538AA1B1D542239008EC33D /* MGLForegroundStyleLayer.h */,
3538AA1C1D542239008EC33D /* MGLForegroundStyleLayer.mm */,
@@ -1297,6 +1318,8 @@
3575798F1D513EF1000B822E /* Layers */ = {
isa = PBXGroup;
children = (
+ 170C43782028D49800863DF0 /* MGLHeatmapColorTests.mm */,
+ 170C43792028D49800863DF0 /* MGLHeatmapStyleLayerTests.mm */,
DA2DBBCC1D51E80400D38FF9 /* MGLStyleLayerTests.h */,
DA2DBBCD1D51E80400D38FF9 /* MGLStyleLayerTests.m */,
DA3C6FF21E2859E700F962BE /* test-Bridging-Header.h */,
@@ -1578,19 +1601,20 @@
DA8848331CBAFB2A00AB86E3 /* Kit */ = {
isa = PBXGroup;
children = (
- 355ADFF91E9281C300F3939D /* Views */,
- 35CE617F1D4165C2004F2359 /* Categories */,
DAD165841CF4D06B001FF4B9 /* Annotations */,
+ 35CE617F1D4165C2004F2359 /* Categories */,
+ DA88487F1CBB033F00AB86E3 /* Fabric */,
+ DA8848881CBB036000AB86E3 /* SMCalloutView */,
DAD165851CF4D08B001FF4B9 /* Telemetry */,
+ 355ADFF91E9281C300F3939D /* Views */,
+ CA55CD3E202C16AA00CE7095 /* MGLCameraChangeReason.h */,
DA704CC01F65A475004B3F28 /* MGLMapAccessibilityElement.h */,
DA704CC11F65A475004B3F28 /* MGLMapAccessibilityElement.mm */,
- DA8848361CBAFB8500AB86E3 /* MGLMapView.h */,
DA17BE2F1CC4BAC300402C41 /* MGLMapView_Private.h */,
+ DA8848361CBAFB8500AB86E3 /* MGLMapView.h */,
+ DA88484A1CBAFB9800AB86E3 /* MGLMapView.mm */,
DA8848371CBAFB8500AB86E3 /* MGLMapView+IBAdditions.h */,
DA737EE01D056A4E005BDA16 /* MGLMapViewDelegate.h */,
- DA88484A1CBAFB9800AB86E3 /* MGLMapView.mm */,
- DA88487F1CBB033F00AB86E3 /* Fabric */,
- DA8848881CBB036000AB86E3 /* SMCalloutView */,
);
name = Kit;
path = src;
@@ -1848,6 +1872,8 @@
DA8848491CBAFB9800AB86E3 /* MGLMapboxEvents.m */,
9620BB361E69FE1700705A1D /* MGLSDKUpdateChecker.h */,
9620BB371E69FE1700705A1D /* MGLSDKUpdateChecker.mm */,
+ AC518DFD201BB55A00EBC820 /* MGLTelemetryConfig.h */,
+ AC518DFE201BB55A00EBC820 /* MGLTelemetryConfig.m */,
);
name = Telemetry;
sourceTree = "<group>";
@@ -1881,6 +1907,8 @@
350098DC1D484E60004B2AF0 /* NSValue+MGLStyleAttributeAdditions.h in Headers */,
DA8848231CBAFA6200AB86E3 /* MGLOfflineStorage_Private.h in Headers */,
404326891D5B9B27007111BD /* MGLAnnotationContainerView_Private.h in Headers */,
+ CA55CD41202C16AA00CE7095 /* MGLCameraChangeReason.h in Headers */,
+ 1FB7DAAF1F2A4DBD00410606 /* MGLVectorSource+MGLAdditions.h in Headers */,
DA88483B1CBAFB8500AB86E3 /* MGLCalloutView.h in Headers */,
35E0CFE61D3E501500188327 /* MGLStyle_Private.h in Headers */,
3510FFF01D6D9D8C00F413B2 /* NSExpression+MGLAdditions.h in Headers */,
@@ -1953,6 +1981,7 @@
DA8847F91CBAFA5100AB86E3 /* MGLPolygon.h in Headers */,
4049C2AC1DB6E05500B3F799 /* MGLPointCollection_Private.h in Headers */,
DA8847F81CBAFA5100AB86E3 /* MGLPointAnnotation.h in Headers */,
+ 8989B17C201A48EB0081CF59 /* MGLHeatmapStyleLayer.h in Headers */,
353933F21D3FB753003F57D7 /* MGLCircleStyleLayer.h in Headers */,
DA8847F31CBAFA5100AB86E3 /* MGLMultiPoint.h in Headers */,
30E578171DAA85520050F07E /* UIImage+MGLAdditions.h in Headers */,
@@ -1968,6 +1997,7 @@
DA8847F51CBAFA5100AB86E3 /* MGLOfflineRegion.h in Headers */,
DA737EE11D056A4E005BDA16 /* MGLMapViewDelegate.h in Headers */,
DA8848851CBB033F00AB86E3 /* FABKitProtocol.h in Headers */,
+ AC518DFF201BB55A00EBC820 /* MGLTelemetryConfig.h in Headers */,
DA88481B1CBAFA6200AB86E3 /* MGLGeometry_Private.h in Headers */,
3510FFF91D6DCC4700F413B2 /* NSCompoundPredicate+MGLAdditions.h in Headers */,
3557F7B01E1D27D300CCA5E6 /* MGLDistanceFormatter.h in Headers */,
@@ -2026,6 +2056,7 @@
96E516EF2000594F00A02306 /* NSArray+MGLAdditions.h in Headers */,
96E516F12000596800A02306 /* NSString+MGLAdditions.h in Headers */,
35E0CFE71D3E501500188327 /* MGLStyle_Private.h in Headers */,
+ CA55CD42202C16AA00CE7095 /* MGLCameraChangeReason.h in Headers */,
DABFB86D1CBE9A0F00D62B32 /* MGLAnnotationImage.h in Headers */,
DABFB8721CBE9A0F00D62B32 /* MGLUserLocation.h in Headers */,
927FBD001F4DB05500F8BF1F /* MGLMapSnapshotter.h in Headers */,
@@ -2056,6 +2087,7 @@
96E516FB20005A4000A02306 /* MGLUserLocationHeadingBeamLayer.h in Headers */,
96E516DC2000547000A02306 /* MGLPolyline_Private.h in Headers */,
353AFA151D65AB17005A69F4 /* NSDate+MGLAdditions.h in Headers */,
+ AC518E00201BB55A00EBC820 /* MGLTelemetryConfig.h in Headers */,
3510FFFA1D6DCC4700F413B2 /* NSCompoundPredicate+MGLAdditions.h in Headers */,
DA72620C1DEEE3480043BB89 /* MGLOpenGLStyleLayer.h in Headers */,
35CE61831D4165D9004F2359 /* UIColor+MGLAdditions.h in Headers */,
@@ -2087,6 +2119,7 @@
DABFB8631CBE99E500D62B32 /* MGLOfflineRegion.h in Headers */,
DA35A2B21CCA141D00E826B2 /* MGLCompassDirectionFormatter.h in Headers */,
DAF0D8141DFE0EC500B28378 /* MGLVectorSource_Private.h in Headers */,
+ 8989B17D201A48EB0081CF59 /* MGLHeatmapStyleLayer.h in Headers */,
DABFB8731CBE9A9900D62B32 /* Mapbox.h in Headers */,
357FE2DE1E02D2B20068B753 /* NSCoder+MGLAdditions.h in Headers */,
1753ED431E53CE6F00A9FD90 /* MGLConversion.h in Headers */,
@@ -2531,6 +2564,8 @@
409F43FD1E9E781C0048729D /* MGLMapViewDelegateIntegrationTests.swift in Sources */,
DA2E88651CC0382C00F24E7B /* MGLStyleTests.mm in Sources */,
DA2E88611CC0382C00F24E7B /* MGLGeometryTests.mm in Sources */,
+ 170C437D2029D97900863DF0 /* MGLHeatmapStyleLayerTests.mm in Sources */,
+ 170C437C2029D96F00863DF0 /* MGLHeatmapColorTests.mm in Sources */,
357579801D501E09000B822E /* MGLFillStyleLayerTests.mm in Sources */,
35D9DDE21DA25EEC00DAAD69 /* MGLCodingTests.m in Sources */,
DA1F8F3D1EBD287B00367E42 /* MGLDocumentationGuideTests.swift in Sources */,
@@ -2643,6 +2678,7 @@
35B82BFA1D6C5F8400B1B721 /* NSPredicate+MGLAdditions.mm in Sources */,
DA8848521CBAFB9800AB86E3 /* MGLAPIClient.m in Sources */,
966FCF4E1F3A5C9200F2B6DE /* MGLUserLocationHeadingBeamLayer.m in Sources */,
+ 8989B17E201A48EB0081CF59 /* MGLHeatmapStyleLayer.mm in Sources */,
DA8848301CBAFA6200AB86E3 /* NSProcessInfo+MGLAdditions.m in Sources */,
353AFA161D65AB17005A69F4 /* NSDate+MGLAdditions.mm in Sources */,
35D13AC51D3D19DD00AFB4E0 /* MGLFillStyleLayer.mm in Sources */,
@@ -2654,6 +2690,7 @@
DA72620D1DEEE3480043BB89 /* MGLOpenGLStyleLayer.mm in Sources */,
DA88481A1CBAFA6200AB86E3 /* MGLAccountManager.m in Sources */,
3510FFFB1D6DCC4700F413B2 /* NSCompoundPredicate+MGLAdditions.mm in Sources */,
+ AC518E03201BB56000EBC820 /* MGLTelemetryConfig.m in Sources */,
DA8848271CBAFA6200AB86E3 /* MGLPolyline.mm in Sources */,
DA8848581CBAFB9800AB86E3 /* MGLMapboxEvents.m in Sources */,
35CE61841D4165D9004F2359 /* UIColor+MGLAdditions.mm in Sources */,
@@ -2734,6 +2771,7 @@
DAA4E4311CBB730400178DFB /* MGLMapboxEvents.m in Sources */,
966FCF4F1F3A5C9200F2B6DE /* MGLUserLocationHeadingBeamLayer.m in Sources */,
DAA4E4231CBB730400178DFB /* MGLPolygon.mm in Sources */,
+ 8989B17F201A48EB0081CF59 /* MGLHeatmapStyleLayer.mm in Sources */,
353AFA171D65AB17005A69F4 /* NSDate+MGLAdditions.mm in Sources */,
35D13AC61D3D19DD00AFB4E0 /* MGLFillStyleLayer.mm in Sources */,
DAA4E42A1CBB730400178DFB /* NSProcessInfo+MGLAdditions.m in Sources */,
@@ -2745,6 +2783,7 @@
DA72620E1DEEE3480043BB89 /* MGLOpenGLStyleLayer.mm in Sources */,
DAA4E42F1CBB730400178DFB /* MGLCompactCalloutView.m in Sources */,
3510FFFC1D6DCC4700F413B2 /* NSCompoundPredicate+MGLAdditions.mm in Sources */,
+ AC518E04201BB56100EBC820 /* MGLTelemetryConfig.m in Sources */,
DAA4E4271CBB730400178DFB /* MGLTilePyramidOfflineRegion.mm in Sources */,
DAA4E41C1CBB730400178DFB /* MGLAccountManager.m in Sources */,
35CE61851D4165D9004F2359 /* UIColor+MGLAdditions.mm in Sources */,
diff --git a/platform/ios/jazzy.yml b/platform/ios/jazzy.yml
index b3662adc70..61e9ad39e8 100644
--- a/platform/ios/jazzy.yml
+++ b/platform/ios/jazzy.yml
@@ -95,6 +95,7 @@ custom_categories:
- MGLCircleStyleLayer
- MGLFillStyleLayer
- MGLFillExtrusionStyleLayer
+ - MGLHeatmapStyleLayer
- MGLHillshadeStyleLayer
- MGLLineStyleLayer
- MGLSymbolStyleLayer
diff --git a/platform/ios/src/MGLCameraChangeReason.h b/platform/ios/src/MGLCameraChangeReason.h
new file mode 100644
index 0000000000..6c6b3636ba
--- /dev/null
+++ b/platform/ios/src/MGLCameraChangeReason.h
@@ -0,0 +1,61 @@
+#import "MGLFoundation.h"
+
+/**
+ :nodoc:
+ Bitmask values that describe why a camera move occurred.
+
+ Values of this type are passed to the `MGLMapView`'s delegate in the following methods:
+
+ - `-mapView:shouldChangeFromCamera:toCamera:reason:`
+ - `-mapView:regionWillChangeWithReason:animated:`
+ - `-mapView:regionIsChangingWithReason:`
+ - `-mapView:regionDidChangeWithReason:animated:`
+
+ It's important to note that it's almost impossible to perform a rotate without zooming (in or out),
+ so if you'll often find `MGLCameraChangeReasonGesturePinch` set alongside `MGLCameraChangeReasonGestureRotate`.
+
+ Since there are several reasons why a zoom or rotation has occurred, it is worth considering
+ creating a combined constant, for example:
+
+ ```
+ static const MGLCameraChangeReason anyZoom = MGLCameraChangeReasonGesturePinch |
+ MGLCameraChangeReasonGestureZoomIn |
+ MGLCameraChangeReasonGestureZoomOut |
+ MGLCameraChangeReasonGestureOneFingerZoom;
+
+ static const MGLCameraChangeReason anyRotation = MGLCameraChangeReasonResetNorth | MGLCameraChangeReasonGestureRotate;
+ ```
+ */
+typedef NS_OPTIONS(NSUInteger, MGLCameraChangeReason)
+{
+ /// :nodoc: The reason for the camera change has not be specified.
+ MGLCameraChangeReasonNone = 0,
+
+ /// :nodoc: Set when a public API that moves the camera is called. This may be set for some gestures,
+ /// for example MGLCameraChangeReasonResetNorth.
+ MGLCameraChangeReasonProgrammatic = 1 << 0,
+
+ /// :nodoc: The user tapped the compass to reset the map orientation so North is up.
+ MGLCameraChangeReasonResetNorth = 1 << 1,
+
+ /// :nodoc: The user panned the map.
+ MGLCameraChangeReasonGesturePan = 1 << 2,
+
+ /// :nodoc: The user pinched to zoom in/out.
+ MGLCameraChangeReasonGesturePinch = 1 << 3,
+
+ // :nodoc: The user rotated the map.
+ MGLCameraChangeReasonGestureRotate = 1 << 4,
+
+ /// :nodoc: The user zoomed the map in (one finger double tap).
+ MGLCameraChangeReasonGestureZoomIn = 1 << 5,
+
+ /// :nodoc: The user zoomed the map out (two finger single tap).
+ MGLCameraChangeReasonGestureZoomOut = 1 << 6,
+
+ /// :nodoc: The user long pressed on the map for a quick zoom (single tap, then long press and drag up/down).
+ MGLCameraChangeReasonGestureOneFingerZoom = 1 << 7,
+
+ // :nodoc: The user panned with two fingers to tilt the map (two finger drag).
+ MGLCameraChangeReasonGestureTilt = 1 << 8
+};
diff --git a/platform/ios/src/MGLLocationManager.m b/platform/ios/src/MGLLocationManager.m
index b0d2e17d5d..85ef4ca489 100644
--- a/platform/ios/src/MGLLocationManager.m
+++ b/platform/ios/src/MGLLocationManager.m
@@ -1,9 +1,9 @@
#import "MGLLocationManager.h"
+#import "MGLTelemetryConfig.h"
#import <UIKit/UIKit.h>
static const NSTimeInterval MGLLocationManagerHibernationTimeout = 300.0;
static const NSTimeInterval MGLLocationManagerHibernationPollInterval = 5.0;
-static const CLLocationDistance MGLLocationManagerHibernationRadius = 300.0;
static const CLLocationDistance MGLLocationManagerDistanceFilter = 5.0;
static NSString * const MGLLocationManagerRegionIdentifier = @"MGLLocationManagerRegionIdentifier.fence.center";
@@ -122,7 +122,7 @@ static NSString * const MGLLocationManagerRegionIdentifier = @"MGLLocationManage
}
- (void)establishRegionMonitoringForLocation:(CLLocation *)location {
- CLCircularRegion *region = [[CLCircularRegion alloc] initWithCenter:location.coordinate radius:MGLLocationManagerHibernationRadius identifier:MGLLocationManagerRegionIdentifier];
+ CLCircularRegion *region = [[CLCircularRegion alloc] initWithCenter:location.coordinate radius:MGLTelemetryConfig.sharedConfig.MGLLocationManagerHibernationRadius identifier:MGLLocationManagerRegionIdentifier];
region.notifyOnEntry = NO;
region.notifyOnExit = YES;
[self.standardLocationManager startMonitoringForRegion:region];
@@ -151,7 +151,7 @@ static NSString * const MGLLocationManagerRegionIdentifier = @"MGLLocationManage
if (location.speed > 0.0) {
[self startBackgroundTimeoutTimer];
}
- if (self.standardLocationManager.monitoredRegions.count == 0 || location.horizontalAccuracy < MGLLocationManagerHibernationRadius) {
+ if (self.standardLocationManager.monitoredRegions.count == 0 || location.horizontalAccuracy < MGLTelemetryConfig.sharedConfig.MGLLocationManagerHibernationRadius) {
[self establishRegionMonitoringForLocation:location];
}
if ([self.delegate respondsToSelector:@selector(locationManager:didUpdateLocations:)]) {
diff --git a/platform/ios/src/MGLMapView.mm b/platform/ios/src/MGLMapView.mm
index 31eac056e5..cad14343a8 100644
--- a/platform/ios/src/MGLMapView.mm
+++ b/platform/ios/src/MGLMapView.mm
@@ -210,6 +210,8 @@ public:
@property (nonatomic) UILongPressGestureRecognizer *quickZoom;
@property (nonatomic) UIPanGestureRecognizer *twoFingerDrag;
+@property (nonatomic) MGLCameraChangeReason cameraChangeReasonBitmask;
+
/// Mapping from reusable identifiers to annotation images.
@property (nonatomic) NS_MUTABLE_DICTIONARY_OF(NSString *, MGLAnnotationImage *) *annotationImagesByIdentifier;
@@ -506,10 +508,6 @@ public:
_doubleTap.numberOfTapsRequired = 2;
[self addGestureRecognizer:_doubleTap];
- _singleTapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleSingleTapGesture:)];
- [_singleTapGestureRecognizer requireGestureRecognizerToFail:_doubleTap];
- _singleTapGestureRecognizer.delegate = self;
- [self addGestureRecognizer:_singleTapGestureRecognizer];
_twoFingerDrag = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handleTwoFingerDragGesture:)];
_twoFingerDrag.minimumNumberOfTouches = 2;
@@ -534,16 +532,26 @@ public:
[_quickZoom requireGestureRecognizerToFail:_doubleTap];
[self addGestureRecognizer:_quickZoom];
+ _singleTapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleSingleTapGesture:)];
+ [_singleTapGestureRecognizer requireGestureRecognizerToFail:_doubleTap];
+ _singleTapGestureRecognizer.delegate = self;
+ [_singleTapGestureRecognizer requireGestureRecognizerToFail:_quickZoom];
+ [self addGestureRecognizer:_singleTapGestureRecognizer];
+
// observe app activity
//
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(willTerminate) name:UIApplicationWillTerminateNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sleepGL:) name:UIApplicationDidEnterBackgroundNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(wakeGL:) name:UIApplicationWillEnterForegroundNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(wakeGL:) name:UIApplicationDidBecomeActiveNotification object:nil];
+ // As of 3.7.5, we intentionally do not listen for `UIApplicationWillResignActiveNotification` or call `sleepGL:` in response to it, as doing
+ // so causes a loop when asking for location permission. See: https://github.com/mapbox/mapbox-gl-native/issues/11225
+
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMemoryWarning) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(deviceOrientationDidChange:) name:UIDeviceOrientationDidChangeNotification object:nil];
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
+
// set initial position
//
mbgl::CameraOptions options;
@@ -551,6 +559,9 @@ public:
mbgl::EdgeInsets padding = MGLEdgeInsetsFromNSEdgeInsets(self.contentInset);
options.padding = padding;
options.zoom = 0;
+
+ _cameraChangeReasonBitmask = MGLCameraChangeReasonNone;
+
_mbglMap->jumpTo(options);
_pendingLatitude = NAN;
_pendingLongitude = NAN;
@@ -706,7 +717,7 @@ public:
{
MGLAssertIsMainThread();
- _rendererFrontend->onLowMemory();
+ _rendererFrontend->reduceMemoryUse();
}
#pragma mark - Layout -
@@ -1148,6 +1159,13 @@ public:
- (void)sleepGL:(__unused NSNotification *)notification
{
MGLAssertIsMainThread();
+
+ // Ideally we would wait until we actually received a memory warning but the bulk of the memory
+ // we have to release is tied up in GL buffers that we can't touch once we're in the background.
+ // Compromise position: release everything but currently rendering tiles
+ // A possible improvement would be to store a copy of the GL buffers that we could use to rapidly
+ // restart, but that we could also discard in response to a memory warning.
+ _rendererFrontend->reduceMemoryUse();
if ( ! self.dormant)
{
@@ -1237,6 +1255,8 @@ public:
- (void)handleCompassTapGesture:(__unused id)sender
{
+ self.cameraChangeReasonBitmask |= MGLCameraChangeReasonResetNorth;
+
[self resetNorthAnimated:YES];
if (self.userTrackingMode == MGLUserTrackingModeFollowWithHeading ||
@@ -1259,6 +1279,7 @@ public:
- (void)notifyGestureDidBegin {
BOOL animated = NO;
+
[self cameraWillChangeAnimated:animated];
_mbglMap->setGestureInProgress(true);
_changeDelimiterSuppressionDepth++;
@@ -1282,6 +1303,23 @@ public:
return _changeDelimiterSuppressionDepth > 0;
}
+- (BOOL)_shouldChangeFromCamera:(nonnull MGLMapCamera *)oldCamera toCamera:(nonnull MGLMapCamera *)newCamera
+{
+ // Check delegates first
+ if ([self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:reason:)])
+ {
+ return [self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:newCamera reason:self.cameraChangeReasonBitmask];
+ }
+ else if ([self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)])
+ {
+ return [self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:newCamera];
+ }
+ else
+ {
+ return YES;
+ }
+}
+
- (void)handlePanGesture:(UIPanGestureRecognizer *)pan
{
if ( ! self.isScrollEnabled) return;
@@ -1289,7 +1327,9 @@ public:
_mbglMap->cancelTransitions();
MGLMapCamera *oldCamera = self.camera;
-
+
+ self.cameraChangeReasonBitmask |= MGLCameraChangeReasonGesturePan;
+
if (pan.state == UIGestureRecognizerStateBegan)
{
[self trackGestureEvent:MGLEventGesturePanStart forRecognizer:pan];
@@ -1303,9 +1343,8 @@ public:
CGPoint delta = [pan translationInView:pan.view];
MGLMapCamera *toCamera = [self cameraByPanningWithTranslation:delta panGesture:pan];
-
- if (![self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)] ||
- [self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:toCamera])
+
+ if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera])
{
_mbglMap->moveBy({ delta.x, delta.y });
[pan setTranslation:CGPointZero inView:pan.view];
@@ -1327,9 +1366,8 @@ public:
{
CGPoint offset = CGPointMake(velocity.x * self.decelerationRate / 4, velocity.y * self.decelerationRate / 4);
MGLMapCamera *toCamera = [self cameraByPanningWithTranslation:offset panGesture:pan];
-
- if (![self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)] ||
- [self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:toCamera])
+
+ if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera])
{
_mbglMap->moveBy({ offset.x, offset.y }, MGLDurationFromTimeInterval(self.decelerationRate));
}
@@ -1360,6 +1398,8 @@ public:
CGPoint centerPoint = [self anchorPointForGesture:pinch];
MGLMapCamera *oldCamera = self.camera;
+ self.cameraChangeReasonBitmask |= MGLCameraChangeReasonGesturePinch;
+
if (pinch.state == UIGestureRecognizerStateBegan)
{
[self trackGestureEvent:MGLEventGesturePinchStart forRecognizer:pinch];
@@ -1376,9 +1416,8 @@ public:
// Calculates the final camera zoom, has no effect within current map camera.
MGLMapCamera *toCamera = [self cameraByZoomingToZoomLevel:newZoom aroundAnchorPoint:centerPoint];
-
- if (![self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)] ||
- [self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:toCamera])
+
+ if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera])
{
_mbglMap->setZoom(newZoom, mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y });
// The gesture recognizer only reports the gesture’s current center
@@ -1430,9 +1469,8 @@ public:
// Calculates the final camera zoom, this has no effect within current map camera.
double zoom = log2(newScale);
MGLMapCamera *toCamera = [self cameraByZoomingToZoomLevel:zoom aroundAnchorPoint:centerPoint];
-
- if ([self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)]
- && ![self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:toCamera])
+
+ if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera])
{
drift = NO;
} else {
@@ -1458,7 +1496,9 @@ public:
CGPoint centerPoint = [self anchorPointForGesture:rotate];
MGLMapCamera *oldCamera = self.camera;
-
+
+ self.cameraChangeReasonBitmask |= MGLCameraChangeReasonGestureRotate;
+
if (rotate.state == UIGestureRecognizerStateBegan)
{
[self trackGestureEvent:MGLEventGestureRotateStart forRecognizer:rotate];
@@ -1485,9 +1525,8 @@ public:
}
MGLMapCamera *toCamera = [self cameraByRotatingToDirection:newDegrees aroundAnchorPoint:centerPoint];
-
- if (![self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)] ||
- [self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:toCamera])
+
+ if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera])
{
_mbglMap->setBearing(newDegrees, mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y });
}
@@ -1505,9 +1544,8 @@ public:
CGFloat newDegrees = MGLDegreesFromRadians(newRadians) * -1;
MGLMapCamera *toCamera = [self cameraByRotatingToDirection:newDegrees aroundAnchorPoint:centerPoint];
-
- if (![self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)] ||
- [self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:toCamera])
+
+ if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera])
{
_mbglMap->setBearing(newDegrees, mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y }, MGLDurationFromTimeInterval(decelerationRate));
@@ -1552,6 +1590,7 @@ public:
}
[self deselectAnnotation:self.selectedAnnotation animated:YES];
UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, nextElement);
+
return;
}
@@ -1562,7 +1601,7 @@ public:
CGRect positionRect = [self positioningRectForAnnotation:annotation defaultCalloutPoint:calloutPoint];
[self selectAnnotation:annotation animated:YES calloutPositioningRect:positionRect];
}
- else
+ else if (self.selectedAnnotation)
{
[self deselectAnnotation:self.selectedAnnotation animated:YES];
}
@@ -1644,6 +1683,8 @@ public:
if (doubleTap.state == UIGestureRecognizerStateEnded)
{
+ self.cameraChangeReasonBitmask |= MGLCameraChangeReasonGestureZoomIn;
+
MGLMapCamera *oldCamera = self.camera;
double newZoom = round(self.zoomLevel) + 1.0;
@@ -1651,9 +1692,8 @@ public:
CGPoint gesturePoint = [self anchorPointForGesture:doubleTap];
MGLMapCamera *toCamera = [self cameraByZoomingToZoomLevel:newZoom aroundAnchorPoint:gesturePoint];
-
- if (![self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)] ||
- [self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:toCamera])
+
+ if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera])
{
[self trackGestureEvent:MGLEventGestureDoubleTap forRecognizer:doubleTap];
@@ -1680,9 +1720,13 @@ public:
_mbglMap->cancelTransitions();
+ self.cameraChangeReasonBitmask |= MGLCameraChangeReasonGestureZoomOut;
+
if (twoFingerTap.state == UIGestureRecognizerStateBegan)
{
[self trackGestureEvent:MGLEventGestureTwoFingerSingleTap forRecognizer:twoFingerTap];
+
+ [self notifyGestureDidBegin];
}
else if (twoFingerTap.state == UIGestureRecognizerStateEnded)
{
@@ -1693,9 +1737,8 @@ public:
CGPoint gesturePoint = [self anchorPointForGesture:twoFingerTap];
MGLMapCamera *toCamera = [self cameraByZoomingToZoomLevel:newZoom aroundAnchorPoint:gesturePoint];
-
- if (![self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)] ||
- [self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:toCamera])
+
+ if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera])
{
mbgl::ScreenCoordinate center(gesturePoint.x, gesturePoint.y);
_mbglMap->setZoom(newZoom, center, MGLDurationFromTimeInterval(MGLAnimationDuration));
@@ -1715,7 +1758,9 @@ public:
if ( ! self.isZoomEnabled) return;
_mbglMap->cancelTransitions();
-
+
+ self.cameraChangeReasonBitmask |= MGLCameraChangeReasonGestureOneFingerZoom;
+
if (quickZoom.state == UIGestureRecognizerStateBegan)
{
[self trackGestureEvent:MGLEventGestureQuickZoom forRecognizer:quickZoom];
@@ -1738,9 +1783,8 @@ public:
MGLMapCamera *oldCamera = self.camera;
MGLMapCamera *toCamera = [self cameraByZoomingToZoomLevel:newZoom aroundAnchorPoint:centerPoint];
-
- if (![self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)] ||
- [self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:toCamera])
+
+ if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera])
{
_mbglMap->setZoom(newZoom, mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y });
}
@@ -1760,6 +1804,8 @@ public:
_mbglMap->cancelTransitions();
+ self.cameraChangeReasonBitmask |= MGLCameraChangeReasonGestureTilt;
+
if (twoFingerDrag.state == UIGestureRecognizerStateBegan)
{
[self trackGestureEvent:MGLEventGesturePitchStart forRecognizer:twoFingerDrag];
@@ -1779,8 +1825,7 @@ public:
MGLMapCamera *oldCamera = self.camera;
MGLMapCamera *toCamera = [self cameraByTiltingToPitch:pitchNew];
- if (![self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)] ||
- [self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:toCamera])
+ if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera])
{
_mbglMap->setPitch(pitchNew, mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y });
}
@@ -2278,7 +2323,7 @@ public:
- (void)emptyMemoryCache
{
- _rendererFrontend->onLowMemory();
+ _rendererFrontend->reduceMemoryUse();
}
- (void)setZoomEnabled:(BOOL)zoomEnabled
@@ -2871,6 +2916,8 @@ public:
{
self.userTrackingMode = MGLUserTrackingModeNone;
+ self.cameraChangeReasonBitmask |= MGLCameraChangeReasonProgrammatic;
+
[self _setCenterCoordinate:centerCoordinate edgePadding:self.contentInset zoomLevel:zoomLevel direction:direction duration:animated ? MGLAnimationDuration : 0 animationTimingFunction:nil completionHandler:completion];
}
@@ -2920,6 +2967,9 @@ public:
}
_mbglMap->cancelTransitions();
+
+ self.cameraChangeReasonBitmask |= MGLCameraChangeReasonProgrammatic;
+
_mbglMap->easeTo(cameraOptions, animationOptions);
}
@@ -2943,6 +2993,8 @@ public:
if (zoomLevel == self.zoomLevel) return;
_mbglMap->cancelTransitions();
+ self.cameraChangeReasonBitmask |= MGLCameraChangeReasonProgrammatic;
+
CGFloat duration = animated ? MGLAnimationDuration : 0;
_mbglMap->setZoom(zoomLevel,
@@ -3031,6 +3083,9 @@ public:
- (void)setVisibleCoordinates:(const CLLocationCoordinate2D *)coordinates count:(NSUInteger)count edgePadding:(UIEdgeInsets)insets direction:(CLLocationDirection)direction duration:(NSTimeInterval)duration animationTimingFunction:(nullable CAMediaTimingFunction *)function completionHandler:(nullable void (^)(void))completion
{
self.userTrackingMode = MGLUserTrackingModeNone;
+
+ self.cameraChangeReasonBitmask |= MGLCameraChangeReasonProgrammatic;
+
[self _setVisibleCoordinates:coordinates count:count edgePadding:insets direction:direction duration:duration animationTimingFunction:function completionHandler:completion];
}
@@ -3080,6 +3135,9 @@ public:
[self willChangeValueForKey:@"visibleCoordinateBounds"];
_mbglMap->cancelTransitions();
+
+ self.cameraChangeReasonBitmask |= MGLCameraChangeReasonProgrammatic;
+
_mbglMap->easeTo(cameraOptions, animationOptions);
[self didChangeValueForKey:@"visibleCoordinateBounds"];
}
@@ -3113,6 +3171,8 @@ public:
CGFloat duration = animated ? MGLAnimationDuration : 0;
+ self.cameraChangeReasonBitmask |= MGLCameraChangeReasonProgrammatic;
+
if (self.userTrackingMode == MGLUserTrackingModeNone)
{
_mbglMap->setBearing(direction,
@@ -3197,6 +3257,9 @@ public:
[self willChangeValueForKey:@"camera"];
_mbglMap->cancelTransitions();
+
+ self.cameraChangeReasonBitmask |= MGLCameraChangeReasonProgrammatic;
+
mbgl::CameraOptions cameraOptions = [self cameraOptionsObjectForAnimatingToCamera:camera edgePadding:edgePadding];
_mbglMap->easeTo(cameraOptions, animationOptions);
[self didChangeValueForKey:@"camera"];
@@ -3253,6 +3316,9 @@ public:
[self willChangeValueForKey:@"camera"];
_mbglMap->cancelTransitions();
+
+ self.cameraChangeReasonBitmask |= MGLCameraChangeReasonProgrammatic;
+
mbgl::CameraOptions cameraOptions = [self cameraOptionsObjectForAnimatingToCamera:camera edgePadding:insets];
_mbglMap->flyTo(cameraOptions, animationOptions);
[self didChangeValueForKey:@"camera"];
@@ -3413,6 +3479,13 @@ public:
return [self metersPerPointAtLatitude:latitude];
}
+#pragma mark - Camera Change Reason -
+
+- (void)resetCameraChangeReason
+{
+ self.cameraChangeReasonBitmask = MGLCameraChangeReasonNone;
+}
+
#pragma mark - Styling -
- (NS_ARRAY_OF(NSURL *) *)bundledStyleURLs
@@ -5359,9 +5432,16 @@ public:
}
}
- if ( ! [self isSuppressingChangeDelimiters] && [self.delegate respondsToSelector:@selector(mapView:regionWillChangeAnimated:)])
+ if ( ! [self isSuppressingChangeDelimiters] )
{
- [self.delegate mapView:self regionWillChangeAnimated:animated];
+ if ([self.delegate respondsToSelector:@selector(mapView:regionWillChangeWithReason:animated:)])
+ {
+ [self.delegate mapView:self regionWillChangeWithReason:self.cameraChangeReasonBitmask animated:animated];
+ }
+ else if ([self.delegate respondsToSelector:@selector(mapView:regionWillChangeAnimated:)])
+ {
+ [self.delegate mapView:self regionWillChangeAnimated:animated];
+ }
}
}
@@ -5375,8 +5455,12 @@ public:
if (!self.scaleBar.hidden) {
[(MGLScaleBar *)self.scaleBar setMetersPerPoint:[self metersPerPointAtLatitude:self.centerCoordinate.latitude]];
}
-
- if ([self.delegate respondsToSelector:@selector(mapViewRegionIsChanging:)])
+
+ if ([self.delegate respondsToSelector:@selector(mapView:regionIsChangingWithReason:)])
+ {
+ [self.delegate mapView:self regionIsChangingWithReason:self.cameraChangeReasonBitmask];
+ }
+ else if ([self.delegate respondsToSelector:@selector(mapViewRegionIsChanging:)])
{
[self.delegate mapViewRegionIsChanging:self];
}
@@ -5389,9 +5473,13 @@ public:
[self updateCompass];
- if ( ! [self isSuppressingChangeDelimiters] && [self.delegate respondsToSelector:@selector(mapView:regionDidChangeAnimated:)])
+ if ( ! [self isSuppressingChangeDelimiters])
{
- if ([UIApplication sharedApplication].applicationState == UIApplicationStateActive)
+ BOOL respondsToSelector = [self.delegate respondsToSelector:@selector(mapView:regionDidChangeAnimated:)];
+ BOOL respondsToSelectorWithReason = [self.delegate respondsToSelector:@selector(mapView:regionDidChangeWithReason:animated:)];
+
+ if ((respondsToSelector || respondsToSelectorWithReason) &&
+ ([UIApplication sharedApplication].applicationState == UIApplicationStateActive))
{
_featureAccessibilityElements = nil;
_visiblePlaceFeatures = nil;
@@ -5403,7 +5491,17 @@ public:
UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, nil);
}
}
- [self.delegate mapView:self regionDidChangeAnimated:animated];
+
+ if (respondsToSelectorWithReason)
+ {
+ [self.delegate mapView:self regionDidChangeWithReason:self.cameraChangeReasonBitmask animated:animated];
+ }
+ else if (respondsToSelector)
+ {
+ [self.delegate mapView:self regionDidChangeAnimated:animated];
+ }
+
+ [self resetCameraChangeReason];
}
}
diff --git a/platform/ios/src/MGLMapViewDelegate.h b/platform/ios/src/MGLMapViewDelegate.h
index 096711fcbb..0368d8413c 100644
--- a/platform/ios/src/MGLMapViewDelegate.h
+++ b/platform/ios/src/MGLMapViewDelegate.h
@@ -1,6 +1,7 @@
#import <UIKit/UIKit.h>
#import "MGLTypes.h"
+#import "MGLCameraChangeReason.h"
NS_ASSUME_NONNULL_BEGIN
@@ -22,17 +23,80 @@ NS_ASSUME_NONNULL_BEGIN
#pragma mark Responding to Map Position Changes
/**
+ Asks the delegate whether the map view should be allowed to change from the
+ existing camera to the new camera in response to a user gesture.
+
+ This method is called as soon as the user gesture is recognized. It is not
+ called in response to a programmatic camera change, such as by setting the
+ `centerCoordinate` property or calling `-flyToCamera:completionHandler:`.
+
+ This method is called many times during gesturing, so you should avoid performing
+ complex or performance-intensive tasks in your implementation.
+
+ @param mapView The map view that the user is manipulating.
+ @param oldCamera The camera representing the viewpoint at the moment the
+ gesture is recognized. If this method returns `NO`, the map view’s camera
+ continues to be this camera.
+ @param newCamera The expected camera after the gesture completes. If this
+ method returns `YES`, this camera becomes the map view’s camera.
+ @return A Boolean value indicating whether the map view should stay at
+ `oldCamera` or change to `newCamera`.
+ */
+- (BOOL)mapView:(MGLMapView *)mapView shouldChangeFromCamera:(MGLMapCamera *)oldCamera toCamera:(MGLMapCamera *)newCamera;
+
+/**
+ :nodoc:
+ Asks the delegate whether the map view should be allowed to change from the
+ existing camera to the new camera in response to a user gesture.
+
+ This method is called as soon as the user gesture is recognized. It is not
+ called in response to a programmatic camera change, such as by setting the
+ `centerCoordinate` property or calling `-flyToCamera:completionHandler:`.
+
+ This method is called many times during gesturing, so you should avoid performing
+ complex or performance-intensive tasks in your implementation.
+
+ @param mapView The map view that the user is manipulating.
+ @param oldCamera The camera representing the viewpoint at the moment the
+ gesture is recognized. If this method returns `NO`, the map view’s camera
+ continues to be this camera.
+ @param newCamera The expected camera after the gesture completes. If this
+ method returns `YES`, this camera becomes the map view’s camera.
+ @param reason The reason for the camera change.
+ @return A Boolean value indicating whether the map view should stay at
+ `oldCamera` or change to `newCamera`.
+
+ @note If this method is implemented `-mapView:shouldChangeFromCamera:toCamera:` will not be called.
+ */
+- (BOOL)mapView:(MGLMapView *)mapView shouldChangeFromCamera:(MGLMapCamera *)oldCamera toCamera:(MGLMapCamera *)newCamera reason:(MGLCameraChangeReason)reason;
+
+/**
Tells the delegate that the viewpoint depicted by the map view is about to change.
This method is called whenever the currently displayed map camera will start
changing for any reason.
-
+
@param mapView The map view whose viewpoint will change.
@param animated Whether the change will cause an animated effect on the map.
*/
- (void)mapView:(MGLMapView *)mapView regionWillChangeAnimated:(BOOL)animated;
/**
+ :nodoc:
+ Tells the delegate that the viewpoint depicted by the map view is about to change.
+
+ This method is called whenever the currently displayed map camera will start
+ changing for any reason.
+
+ @param mapView The map view whose viewpoint will change.
+ @param animated Whether the change will cause an animated effect on the map.
+ @param reason The reason for the camera change.
+
+ @note If this method is implemented `-mapView:regionWillChangeAnimated:` will not be called.
+ */
+- (void)mapView:(MGLMapView *)mapView regionWillChangeWithReason:(MGLCameraChangeReason)reason animated:(BOOL)animated;
+
+/**
Tells the delegate that the viewpoint depicted by the map view is changing.
This method is called as the currently displayed map camera changes as part of
@@ -49,6 +113,26 @@ NS_ASSUME_NONNULL_BEGIN
- (void)mapViewRegionIsChanging:(MGLMapView *)mapView;
/**
+ :nodoc:
+ Tells the delegate that the viewpoint depicted by the map view is changing.
+
+ This method is called as the currently displayed map camera changes as part of
+ an animation, whether due to a user gesture or due to a call to a method such
+ as `-[MGLMapView setCamera:animated:]`. This method can be called before
+ `-mapViewDidFinishLoadingMap:` is called.
+
+ During the animation, this method may be called many times to report updates to
+ the viewpoint. Therefore, your implementation of this method should be as lightweight
+ as possible to avoid affecting performance.
+
+ @param mapView The map view whose viewpoint is changing.
+ @param reason The reason for the camera change.
+
+ @note If this method is implemented `-mapViewRegionIsChanging:` will not be called.
+ */
+- (void)mapView:(MGLMapView *)mapView regionIsChangingWithReason:(MGLCameraChangeReason)reason;
+
+/**
Tells the delegate that the viewpoint depicted by the map view has finished
changing.
@@ -62,26 +146,21 @@ NS_ASSUME_NONNULL_BEGIN
- (void)mapView:(MGLMapView *)mapView regionDidChangeAnimated:(BOOL)animated;
/**
- Asks the delegate whether the map view should be allowed to change from the
- existing camera to the new camera in response to a user gesture.
-
- This method is called as soon as the user gesture is recognized. It is not
- called in response to a programmatic camera change, such as by setting the
- `centerCoordinate` property or calling `-flyToCamera:completionHandler:`.
-
- This method is called many times during gesturing, so you should avoid performing
- complex or performance-intensive tasks in your implementation.
-
- @param mapView The map view that the user is manipulating.
- @param oldCamera The camera representing the viewpoint at the moment the
- gesture is recognized. If this method returns `NO`, the map view’s camera
- continues to be this camera.
- @param newCamera The expected camera after the gesture completes. If this
- method returns `YES`, this camera becomes the map view’s camera.
- @return A Boolean value indicating whether the map view should stay at
- `oldCamera` or change to `newCamera`.
+ :nodoc:
+ Tells the delegate that the viewpoint depicted by the map view has finished
+ changing.
+
+ This method is called whenever the currently displayed map camera has finished
+ changing, after any calls to `-mapViewRegionIsChanging:` due to animation. Therefore,
+ this method can be called before `-mapViewDidFinishLoadingMap:` is called.
+
+ @param mapView The map view whose viewpoint has changed.
+ @param animated Whether the change caused an animated effect on the map.
+ @param reason The reason for the camera change.
+
+ @note If this method is implemented `-mapView:regionDidChangeAnimated:` will not be called.
*/
-- (BOOL)mapView:(MGLMapView *)mapView shouldChangeFromCamera:(MGLMapCamera *)oldCamera toCamera:(MGLMapCamera *)newCamera;
+- (void)mapView:(MGLMapView *)mapView regionDidChangeWithReason:(MGLCameraChangeReason)reason animated:(BOOL)animated;
#pragma mark Loading the Map
diff --git a/platform/ios/src/MGLMapboxEvents.m b/platform/ios/src/MGLMapboxEvents.m
index d59972f5bf..273af5b3bc 100644
--- a/platform/ios/src/MGLMapboxEvents.m
+++ b/platform/ios/src/MGLMapboxEvents.m
@@ -6,6 +6,7 @@
#import "NSException+MGLAdditions.h"
#import "MGLAPIClient.h"
#import "MGLLocationManager.h"
+#import "MGLTelemetryConfig.h"
#include <mbgl/storage/reachability.h>
#include <sys/sysctl.h>
@@ -172,6 +173,8 @@ const NSTimeInterval MGLFlushInterval = 180;
- (instancetype) init {
self = [super init];
if (self) {
+ [MGLTelemetryConfig.sharedConfig configurationFromKey:[[NSUserDefaults standardUserDefaults] objectForKey:MGLMapboxMetricsProfile]];
+
_currentAccountTypeValue = @0;
_currentMetricsEnabledValue = YES;
diff --git a/platform/ios/src/MGLTelemetryConfig.h b/platform/ios/src/MGLTelemetryConfig.h
new file mode 100644
index 0000000000..527d344291
--- /dev/null
+++ b/platform/ios/src/MGLTelemetryConfig.h
@@ -0,0 +1,18 @@
+#import <Foundation/Foundation.h>
+#import <CoreLocation/CoreLocation.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface MGLTelemetryConfig : NSObject
+
+@property (nonatomic) CLLocationDistance MGLLocationManagerHibernationRadius;
+
+extern NSString *const MGLMapboxMetricsProfile;
+
++ (nullable instancetype)sharedConfig;
+
+- (void)configurationFromKey:(NSString *)key;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/platform/ios/src/MGLTelemetryConfig.m b/platform/ios/src/MGLTelemetryConfig.m
new file mode 100644
index 0000000000..828bafb14f
--- /dev/null
+++ b/platform/ios/src/MGLTelemetryConfig.m
@@ -0,0 +1,35 @@
+#import "MGLTelemetryConfig.h"
+
+static const CLLocationDistance MGLConfigHibernationRadiusDefault = 300.0;
+static const CLLocationDistance MGLConfigHibernationRadiusWide = 600.0;
+
+NSString *const MGLMapboxMetricsProfile = @"MGLMapboxMetricsProfile";
+
+static NSString *const MGLConfigHibernationRadiusWideKey = @"WideGeoFence";
+
+@implementation MGLTelemetryConfig
+
+- (instancetype) init {
+ self = [super init];
+ if (self) {
+ _MGLLocationManagerHibernationRadius = MGLConfigHibernationRadiusDefault;
+ }
+ return self;
+}
+
++ (nullable instancetype)sharedConfig {
+ static dispatch_once_t onceToken;
+ static MGLTelemetryConfig *_sharedConfig;
+ dispatch_once(&onceToken, ^{
+ _sharedConfig = [[self alloc] init];
+ });
+ return _sharedConfig;
+}
+
+- (void)configurationFromKey:(NSString *)key {
+ if ([key isEqualToString:MGLConfigHibernationRadiusWideKey]) {
+ _MGLLocationManagerHibernationRadius = MGLConfigHibernationRadiusWide;
+ }
+}
+
+@end
diff --git a/platform/ios/src/Mapbox.h b/platform/ios/src/Mapbox.h
index d105189bb8..11720ac68e 100644
--- a/platform/ios/src/Mapbox.h
+++ b/platform/ios/src/Mapbox.h
@@ -45,6 +45,7 @@ FOUNDATION_EXPORT MGL_EXPORT const unsigned char MapboxVersionString[];
#import "MGLSymbolStyleLayer.h"
#import "MGLRasterStyleLayer.h"
#import "MGLCircleStyleLayer.h"
+#import "MGLHeatmapStyleLayer.h"
#import "MGLHillshadeStyleLayer.h"
#import "MGLBackgroundStyleLayer.h"
#import "MGLOpenGLStyleLayer.h"
diff --git a/platform/ios/test/MGLMapViewDelegateIntegrationTests.swift b/platform/ios/test/MGLMapViewDelegateIntegrationTests.swift
index 50f101e86b..4d11b000b9 100644
--- a/platform/ios/test/MGLMapViewDelegateIntegrationTests.swift
+++ b/platform/ios/test/MGLMapViewDelegateIntegrationTests.swift
@@ -13,6 +13,10 @@ extension MGLMapViewDelegateIntegrationTests: MGLMapViewDelegate {
func mapViewRegionIsChanging(_ mapView: MGLMapView) {}
+ func mapViewRegionIsChanging(_ mapView: MGLMapView, reason: MGLCameraChangeReason) {}
+
+ func mapView(_ mapView: MGLMapView, regionIsChangingWith reason: MGLCameraChangeReason) {}
+
func mapView(_ mapView: MGLMapView, didChange mode: MGLUserTrackingMode, animated: Bool) {}
func mapViewDidFinishLoadingMap(_ mapView: MGLMapView) {}
@@ -33,10 +37,16 @@ extension MGLMapViewDelegateIntegrationTests: MGLMapViewDelegate {
func mapView(_ mapView: MGLMapView, didDeselect annotation: MGLAnnotation) {}
+ func mapView(_ mapView: MGLMapView, didSingleTapAt coordinate: CLLocationCoordinate2D) {}
+
func mapView(_ mapView: MGLMapView, regionDidChangeAnimated animated: Bool) {}
+ func mapView(_ mapView: MGLMapView, regionDidChangeWith reason: MGLCameraChangeReason, animated: Bool) {}
+
func mapView(_ mapView: MGLMapView, regionWillChangeAnimated animated: Bool) {}
+ func mapView(_ mapView: MGLMapView, regionWillChangeWith reason: MGLCameraChangeReason, animated: Bool) {}
+
func mapViewDidFailLoadingMap(_ mapView: MGLMapView, withError error: Error) {}
func mapView(_ mapView: MGLMapView, didUpdate userLocation: MGLUserLocation?) {}
@@ -79,4 +89,5 @@ extension MGLMapViewDelegateIntegrationTests: MGLMapViewDelegate {
func mapView(_ mapView: MGLMapView, shouldChangeFrom oldCamera: MGLMapCamera, to newCamera: MGLMapCamera) -> Bool { return false }
+ func mapView(_ mapView: MGLMapView, shouldChangeFrom oldCamera: MGLMapCamera, to newCamera: MGLMapCamera, reason: MGLCameraChangeReason) -> Bool { return false }
}
diff --git a/platform/ios/test/MGLMapViewLayoutTests.m b/platform/ios/test/MGLMapViewLayoutTests.m
index a41e7695f9..e707cfdb41 100644
--- a/platform/ios/test/MGLMapViewLayoutTests.m
+++ b/platform/ios/test/MGLMapViewLayoutTests.m
@@ -67,7 +67,7 @@
CGFloat bottomSafeAreaInset = 0.0;
double accuracy = 0.01;
- if ( [self.mapView respondsToSelector:@selector(safeAreaInsets)] ) {
+ if (@available(iOS 11.0, *)) {
bottomSafeAreaInset = self.mapView.safeAreaInsets.bottom;
}
diff --git a/platform/ios/uitest/MapViewTests.m b/platform/ios/uitest/MapViewTests.m
index 4ed3d89399..ba15af918a 100644
--- a/platform/ios/uitest/MapViewTests.m
+++ b/platform/ios/uitest/MapViewTests.m
@@ -538,10 +538,13 @@
userInfo:@{ @"animated" : @(animated) }];
}
-- (void)mapView:(MGLMapView *)mapView regionDidChangeAnimated:(BOOL)animated {
+- (void)mapView:(MGLMapView *)mapView regionDidChangeWithReason:(MGLCameraChangeReason)reason animated:(BOOL)animated {
+
[[NSNotificationCenter defaultCenter] postNotificationName:@"regionDidChangeAnimated"
object:mapView
- userInfo:@{ @"animated" : @(animated) }];
+ userInfo:@{ @"animated" : @(animated),
+ @"reason" : @(reason)
+ }];
}
- (void)testDelegatesStartStopLocatingUser {
diff --git a/platform/macos/CHANGELOG.md b/platform/macos/CHANGELOG.md
index c8373b2bf5..1f671cb82a 100644
--- a/platform/macos/CHANGELOG.md
+++ b/platform/macos/CHANGELOG.md
@@ -4,12 +4,14 @@
### Styles and rendering
+* Added support for a new layer type: `MGLHeatmapStyleLayer`, a powerful way to visualize point data distributions using heatmaps, fully customizable through runtime styling. [#11046](https://github.com/mapbox/mapbox-gl-native/pull/11046)
* The layout and paint properties on subclasses of `MGLStyleLayer` are now of type `NSExpression` instead of `MGLStyleValue`. A new “Predicates and Expressions” guide provides an overview of the supported operators. ([#10726](https://github.com/mapbox/mapbox-gl-native/pull/10726))
* Added an `MGLComputedShapeSource` class that allows applications to supply vector data to a style layer on a per-tile basis. ([#9983](https://github.com/mapbox/mapbox-gl-native/pull/9983))
* A style can now display smooth hillshading and customize its appearance at runtime using the `MGLHillshadeStyleLayer` class. Hillshading is based on a rasterized digital elevation model supplied by the `MGLRasterDEMSource` class. ([#10642](https://github.com/mapbox/mapbox-gl-native/pull/10642))
* Properties such as `MGLSymbolStyleLayer.iconAllowsOverlap` and `MGLSymbolStyleLayer.iconIgnoresPlacement` now account for symbols in other sources. ([#10436](https://github.com/mapbox/mapbox-gl-native/pull/10436))
* Improved the reliability of collision detection between symbols near the edges of tiles, as well as between symbols when the map is tilted. It is no longer necessary to enable `MGLSymbolStyleLayer.symbolAvoidsEdges` to prevent symbols in adjacent tiles from overlapping with each other. ([#10436](https://github.com/mapbox/mapbox-gl-native/pull/10436))
* Symbols can fade in and out as the map pans, rotates, or tilts. ([#10436](https://github.com/mapbox/mapbox-gl-native/pull/10436))
+* Added the `MGLTileSourceOptionTileCoordinateBounds` option to create an `MGLTileSource` that only supplies tiles within a specific geographic bounding box. ([#11141](https://github.com/mapbox/mapbox-gl-native/pull/11141))
* Fixed an issue preventing a dynamically-added `MGLRasterStyleLayer` from drawing until the map pans. ([#10270](https://github.com/mapbox/mapbox-gl-native/pull/10270))
* Fixed an issue preventing `MGLImageSource`s from drawing on the map when the map is zoomed in and tilted. ([#10677](https://github.com/mapbox/mapbox-gl-native/pull/10677))
@@ -21,6 +23,11 @@
* Fixed a memory leak that occurred when creating a map snapshot. ([#10585](https://github.com/mapbox/mapbox-gl-native/pull/10585))
+## v0.6.2
+
+* Added the `MGLTileSourceOptionTileCoordinateBounds` option to create an `MGLTileSource` that only supplies tiles within a specific geographic bounding box. ([#11141](https://github.com/mapbox/mapbox-gl-native/pull/11141))
+* Fixed an issue that caused `-[MGLMapSnapshotter pointForCoordinate:]` to return the wrong point. ([#11035](https://github.com/mapbox/mapbox-gl-native/pull/11035))
+
## v0.6.1 - January 16, 2018
This version of the Mapbox macOS SDK corresponds to version 3.7.3 of the Mapbox Maps SDK for iOS.
diff --git a/platform/macos/app/Assets.xcassets/Layers/heatmap.imageset/Contents.json b/platform/macos/app/Assets.xcassets/Layers/heatmap.imageset/Contents.json
new file mode 100644
index 0000000000..10226ca23a
--- /dev/null
+++ b/platform/macos/app/Assets.xcassets/Layers/heatmap.imageset/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "filename" : "heatmap.pdf",
+ "language-direction" : "left-to-right"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ },
+ "properties" : {
+ "template-rendering-intent" : "template"
+ }
+} \ No newline at end of file
diff --git a/platform/macos/app/Assets.xcassets/Layers/heatmap.imageset/heatmap.pdf b/platform/macos/app/Assets.xcassets/Layers/heatmap.imageset/heatmap.pdf
new file mode 100644
index 0000000000..88195c3735
--- /dev/null
+++ b/platform/macos/app/Assets.xcassets/Layers/heatmap.imageset/heatmap.pdf
Binary files differ
diff --git a/platform/macos/app/MapDocument.m b/platform/macos/app/MapDocument.m
index 602ed1aa41..7d39f93347 100644
--- a/platform/macos/app/MapDocument.m
+++ b/platform/macos/app/MapDocument.m
@@ -726,8 +726,14 @@ NS_ARRAY_OF(id <MGLAnnotation>) *MBXFlattenedShapes(NS_ARRAY_OF(id <MGLAnnotatio
[self.undoManager setActionName:@"Add Graticule"];
}
+ NSDictionary *sourceOptions = @{
+ MGLShapeSourceOptionMaximumZoomLevel:@14,
+ MGLShapeSourceOptionWrapsCoordinates: @YES,
+ MGLShapeSourceOptionClipsCoordinates: @YES,
+ };
MGLComputedShapeSource *source = [[MGLComputedShapeSource alloc] initWithIdentifier:@"graticule"
- options:@{MGLShapeSourceOptionMaximumZoomLevel:@14}];
+ options:sourceOptions];
+
source.dataSource = self;
[self.mapView.style addSource:source];
MGLLineStyleLayer *lineLayer = [[MGLLineStyleLayer alloc] initWithIdentifier:@"graticule.lines"
diff --git a/platform/macos/app/StyleLayerIconTransformer.m b/platform/macos/app/StyleLayerIconTransformer.m
index 50fe06a2c6..199de86d8b 100644
--- a/platform/macos/app/StyleLayerIconTransformer.m
+++ b/platform/macos/app/StyleLayerIconTransformer.m
@@ -31,6 +31,9 @@
if ([layer isKindOfClass:[MGLSymbolStyleLayer class]]) {
return [NSImage imageNamed:@"symbol"];
}
+ if ([layer isKindOfClass:[MGLHeatmapStyleLayer class]]) {
+ return [NSImage imageNamed:@"heatmap"];
+ }
if ([layer isKindOfClass:[MGLHillshadeStyleLayer class]]) {
return [NSImage imageNamed:@"hillshade"];
}
diff --git a/platform/macos/app/heatmap.json b/platform/macos/app/heatmap.json
new file mode 100644
index 0000000000..6469e57022
--- /dev/null
+++ b/platform/macos/app/heatmap.json
@@ -0,0 +1,809 @@
+{
+ "version": 8,
+ "name": "Basic Heatmap",
+ "center": [
+ 30.49860107152665,
+ 50.459868549177486
+ ],
+ "zoom": 14.033276876197775,
+ "bearing": 0,
+ "pitch": 0,
+ "sources": {
+ "mapbox": {
+ "url": "mapbox://mapbox.mapbox-streets-v7",
+ "type": "vector"
+ }
+ },
+ "sprite": "mapbox://sprites/mourner/cjcgg2bl16cf42snvcbbaf09z",
+ "glyphs": "mapbox://fonts/mourner/{fontstack}/{range}.pbf",
+ "layers": [
+ {
+ "id": "background",
+ "type": "background",
+ "paint": {
+ "background-color": "#dedede"
+ }
+ },
+ {
+ "id": "landuse_overlay_national_park",
+ "type": "fill",
+ "source": "mapbox",
+ "source-layer": "landuse_overlay",
+ "filter": [
+ "==",
+ "class",
+ "national_park"
+ ],
+ "paint": {
+ "fill-color": "#d2edae",
+ "fill-opacity": 0.75
+ }
+ },
+ {
+ "id": "landuse_park",
+ "type": "fill",
+ "source": "mapbox",
+ "source-layer": "landuse",
+ "filter": [
+ "==",
+ "class",
+ "park"
+ ],
+ "paint": {
+ "fill-color": "#d2edae"
+ }
+ },
+ {
+ "id": "waterway",
+ "type": "line",
+ "source": "mapbox",
+ "source-layer": "waterway",
+ "filter": [
+ "all",
+ [
+ "==",
+ "$type",
+ "LineString"
+ ],
+ [
+ "in",
+ "class",
+ "canal",
+ "river"
+ ]
+ ],
+ "paint": {
+ "line-color": "#a0cfdf",
+ "line-width": {
+ "base": 1.4,
+ "stops": [
+ [
+ 8,
+ 0.5
+ ],
+ [
+ 20,
+ 15
+ ]
+ ]
+ }
+ }
+ },
+ {
+ "id": "water",
+ "type": "fill",
+ "source": "mapbox",
+ "source-layer": "water",
+ "paint": {
+ "fill-color": "#a0cfdf"
+ }
+ },
+ {
+ "id": "building",
+ "type": "fill",
+ "source": "mapbox",
+ "source-layer": "building",
+ "paint": {
+ "fill-color": "#d6d6d6"
+ }
+ },
+ {
+ "id": "tunnel_minor",
+ "type": "line",
+ "source": "mapbox",
+ "source-layer": "road",
+ "filter": [
+ "all",
+ [
+ "==",
+ "$type",
+ "LineString"
+ ],
+ [
+ "all",
+ [
+ "==",
+ "structure",
+ "tunnel"
+ ],
+ [
+ "in",
+ "class",
+ "link",
+ "motorway_link",
+ "path",
+ "pedestrian",
+ "service",
+ "street",
+ "street_limited",
+ "track"
+ ]
+ ]
+ ],
+ "layout": {
+ "line-cap": "butt",
+ "line-join": "miter"
+ },
+ "paint": {
+ "line-color": "#efefef",
+ "line-width": {
+ "base": 1.55,
+ "stops": [
+ [
+ 4,
+ 0.25
+ ],
+ [
+ 20,
+ 30
+ ]
+ ]
+ },
+ "line-dasharray": [
+ 0.36,
+ 0.18
+ ]
+ }
+ },
+ {
+ "id": "tunnel_major",
+ "type": "line",
+ "source": "mapbox",
+ "source-layer": "road",
+ "filter": [
+ "all",
+ [
+ "==",
+ "$type",
+ "LineString"
+ ],
+ [
+ "all",
+ [
+ "==",
+ "structure",
+ "tunnel"
+ ],
+ [
+ "in",
+ "class",
+ "motorway",
+ "primary",
+ "secondary",
+ "tertiary",
+ "trunk"
+ ]
+ ]
+ ],
+ "layout": {
+ "line-cap": "butt",
+ "line-join": "miter"
+ },
+ "paint": {
+ "line-color": "#fff",
+ "line-width": {
+ "base": 1.4,
+ "stops": [
+ [
+ 6,
+ 0.5
+ ],
+ [
+ 20,
+ 30
+ ]
+ ]
+ },
+ "line-dasharray": [
+ 0.28,
+ 0.14
+ ]
+ }
+ },
+ {
+ "id": "road_minor",
+ "type": "line",
+ "source": "mapbox",
+ "source-layer": "road",
+ "filter": [
+ "all",
+ [
+ "==",
+ "$type",
+ "LineString"
+ ],
+ [
+ "all",
+ [
+ "in",
+ "class",
+ "link",
+ "motorway_link",
+ "path",
+ "pedestrian",
+ "service",
+ "street",
+ "street_limited",
+ "track"
+ ],
+ [
+ "in",
+ "structure",
+ "ford",
+ "none"
+ ]
+ ]
+ ],
+ "layout": {
+ "line-cap": "round",
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#efefef",
+ "line-width": {
+ "base": 1.55,
+ "stops": [
+ [
+ 4,
+ 0.25
+ ],
+ [
+ 20,
+ 30
+ ]
+ ]
+ }
+ }
+ },
+ {
+ "id": "road_major",
+ "type": "line",
+ "source": "mapbox",
+ "source-layer": "road",
+ "filter": [
+ "all",
+ [
+ "==",
+ "$type",
+ "LineString"
+ ],
+ [
+ "all",
+ [
+ "in",
+ "class",
+ "motorway",
+ "primary",
+ "secondary",
+ "tertiary",
+ "trunk"
+ ],
+ [
+ "in",
+ "structure",
+ "ford",
+ "none"
+ ]
+ ]
+ ],
+ "layout": {
+ "line-cap": "round",
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#fff",
+ "line-width": {
+ "base": 1.4,
+ "stops": [
+ [
+ 6,
+ 0.5
+ ],
+ [
+ 20,
+ 30
+ ]
+ ]
+ }
+ }
+ },
+ {
+ "id": "bridge_minor case",
+ "type": "line",
+ "source": "mapbox",
+ "source-layer": "road",
+ "filter": [
+ "all",
+ [
+ "==",
+ "$type",
+ "LineString"
+ ],
+ [
+ "all",
+ [
+ "==",
+ "structure",
+ "bridge"
+ ],
+ [
+ "in",
+ "class",
+ "link",
+ "motorway_link",
+ "path",
+ "pedestrian",
+ "service",
+ "street",
+ "street_limited",
+ "track"
+ ]
+ ]
+ ],
+ "layout": {
+ "line-cap": "butt",
+ "line-join": "miter"
+ },
+ "paint": {
+ "line-color": "#dedede",
+ "line-width": {
+ "base": 1.6,
+ "stops": [
+ [
+ 12,
+ 0.5
+ ],
+ [
+ 20,
+ 10
+ ]
+ ]
+ },
+ "line-gap-width": {
+ "base": 1.55,
+ "stops": [
+ [
+ 4,
+ 0.25
+ ],
+ [
+ 20,
+ 30
+ ]
+ ]
+ }
+ }
+ },
+ {
+ "id": "bridge_major case",
+ "type": "line",
+ "source": "mapbox",
+ "source-layer": "road",
+ "filter": [
+ "all",
+ [
+ "==",
+ "$type",
+ "LineString"
+ ],
+ [
+ "all",
+ [
+ "==",
+ "structure",
+ "bridge"
+ ],
+ [
+ "in",
+ "class",
+ "motorway",
+ "primary",
+ "secondary",
+ "tertiary",
+ "trunk"
+ ]
+ ]
+ ],
+ "layout": {
+ "line-cap": "butt",
+ "line-join": "miter"
+ },
+ "paint": {
+ "line-color": "#dedede",
+ "line-width": {
+ "base": 1.6,
+ "stops": [
+ [
+ 12,
+ 0.5
+ ],
+ [
+ 20,
+ 10
+ ]
+ ]
+ },
+ "line-gap-width": {
+ "base": 1.55,
+ "stops": [
+ [
+ 4,
+ 0.25
+ ],
+ [
+ 20,
+ 30
+ ]
+ ]
+ }
+ }
+ },
+ {
+ "id": "bridge_minor",
+ "type": "line",
+ "source": "mapbox",
+ "source-layer": "road",
+ "filter": [
+ "all",
+ [
+ "==",
+ "$type",
+ "LineString"
+ ],
+ [
+ "all",
+ [
+ "==",
+ "structure",
+ "bridge"
+ ],
+ [
+ "in",
+ "class",
+ "link",
+ "motorway_link",
+ "path",
+ "pedestrian",
+ "service",
+ "street",
+ "street_limited",
+ "track"
+ ]
+ ]
+ ],
+ "layout": {
+ "line-cap": "round",
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#efefef",
+ "line-width": {
+ "base": 1.55,
+ "stops": [
+ [
+ 4,
+ 0.25
+ ],
+ [
+ 20,
+ 30
+ ]
+ ]
+ }
+ }
+ },
+ {
+ "id": "bridge_major",
+ "type": "line",
+ "source": "mapbox",
+ "source-layer": "road",
+ "filter": [
+ "all",
+ [
+ "==",
+ "$type",
+ "LineString"
+ ],
+ [
+ "all",
+ [
+ "==",
+ "structure",
+ "bridge"
+ ],
+ [
+ "in",
+ "class",
+ "motorway",
+ "primary",
+ "secondary",
+ "tertiary",
+ "trunk"
+ ]
+ ]
+ ],
+ "layout": {
+ "line-cap": "round",
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#fff",
+ "line-width": {
+ "base": 1.4,
+ "stops": [
+ [
+ 6,
+ 0.5
+ ],
+ [
+ 20,
+ 30
+ ]
+ ]
+ }
+ }
+ },
+ {
+ "id": "admin_country",
+ "type": "line",
+ "source": "mapbox",
+ "source-layer": "admin",
+ "filter": [
+ "all",
+ [
+ "==",
+ "$type",
+ "LineString"
+ ],
+ [
+ "all",
+ [
+ "<=",
+ "admin_level",
+ 2
+ ],
+ [
+ "==",
+ "maritime",
+ 0
+ ]
+ ]
+ ],
+ "layout": {
+ "line-cap": "round",
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#8b8a8a",
+ "line-width": {
+ "base": 1.3,
+ "stops": [
+ [
+ 3,
+ 0.5
+ ],
+ [
+ 22,
+ 15
+ ]
+ ]
+ }
+ }
+ },
+ {
+ "id": "road_major_label",
+ "type": "symbol",
+ "source": "mapbox",
+ "source-layer": "road_label",
+ "filter": [
+ "all",
+ [
+ "==",
+ "$type",
+ "LineString"
+ ],
+ [
+ "in",
+ "class",
+ "motorway",
+ "primary",
+ "secondary",
+ "tertiary",
+ "trunk"
+ ]
+ ],
+ "layout": {
+ "symbol-placement": "line",
+ "text-field": "{name_en}",
+ "text-font": [
+ "Open Sans Semibold",
+ "Arial Unicode MS Bold"
+ ],
+ "text-transform": "uppercase",
+ "text-letter-spacing": 0.1,
+ "text-size": {
+ "base": 1.4,
+ "stops": [
+ [
+ 10,
+ 8
+ ],
+ [
+ 20,
+ 14
+ ]
+ ]
+ }
+ },
+ "paint": {
+ "text-color": "#666",
+ "text-halo-color": "rgba(255,255,255,0.75)",
+ "text-halo-width": 2
+ }
+ },
+ {
+ "id": "place_label_other",
+ "type": "symbol",
+ "source": "mapbox",
+ "source-layer": "place_label",
+ "minzoom": 8,
+ "filter": [
+ "all",
+ [
+ "==",
+ "$type",
+ "Point"
+ ],
+ [
+ "in",
+ "type",
+ "hamlet",
+ "island",
+ "neighbourhood",
+ "suburb",
+ "town",
+ "village"
+ ]
+ ],
+ "layout": {
+ "text-field": "{name_en}",
+ "text-font": [
+ "Open Sans Semibold",
+ "Arial Unicode MS Bold"
+ ],
+ "text-max-width": 6,
+ "text-size": {
+ "stops": [
+ [
+ 6,
+ 12
+ ],
+ [
+ 12,
+ 16
+ ]
+ ]
+ }
+ },
+ "paint": {
+ "text-color": "#666",
+ "text-halo-color": "rgba(255,255,255,0.75)",
+ "text-halo-width": 1,
+ "text-halo-blur": 1
+ }
+ },
+ {
+ "id": "place_label_city",
+ "type": "symbol",
+ "source": "mapbox",
+ "source-layer": "place_label",
+ "maxzoom": 16,
+ "filter": [
+ "all",
+ [
+ "==",
+ "$type",
+ "Point"
+ ],
+ [
+ "==",
+ "type",
+ "city"
+ ]
+ ],
+ "layout": {
+ "text-field": "{name_en}",
+ "text-font": [
+ "Open Sans Bold",
+ "Arial Unicode MS Bold"
+ ],
+ "text-max-width": 10,
+ "text-size": {
+ "stops": [
+ [
+ 3,
+ 12
+ ],
+ [
+ 8,
+ 16
+ ]
+ ]
+ }
+ },
+ "paint": {
+ "text-color": "#666",
+ "text-halo-color": "rgba(255,255,255,0.75)",
+ "text-halo-width": 1,
+ "text-halo-blur": 1
+ }
+ },
+ {
+ "id": "country_label",
+ "type": "symbol",
+ "source": "mapbox",
+ "source-layer": "country_label",
+ "maxzoom": 12,
+ "filter": [
+ "==",
+ "$type",
+ "Point"
+ ],
+ "layout": {
+ "text-field": "{name_en}",
+ "text-font": [
+ "Open Sans Regular",
+ "Arial Unicode MS Regular"
+ ],
+ "text-max-width": 10,
+ "text-size": {
+ "stops": [
+ [
+ 3,
+ 14
+ ],
+ [
+ 8,
+ 22
+ ]
+ ]
+ }
+ },
+ "paint": {
+ "text-color": "#666",
+ "text-halo-color": "rgba(255,255,255,0.75)",
+ "text-halo-width": 1,
+ "text-halo-blur": 1
+ }
+ },
+ {
+ "id": "road-heatmap",
+ "type": "heatmap",
+ "source": "mapbox",
+ "source-layer": "road",
+ "paint": {
+ "heatmap-intensity": 1
+ }
+ }
+ ]
+}
diff --git a/platform/macos/app/resources/heatmap.svg b/platform/macos/app/resources/heatmap.svg
new file mode 100644
index 0000000000..fa2a46590a
--- /dev/null
+++ b/platform/macos/app/resources/heatmap.svg
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="16"
+ height="16"
+ viewBox="0 0 16 16"
+ id="svg4148"
+ version="1.1"
+ inkscape:version="0.92.2 5c3e80d, 2017-08-06"
+ sodipodi:docname="heatmap.svg"
+ inkscape:export-filename="/Users/mxn/Desktop/symbol.png"
+ inkscape:export-xdpi="90.000244"
+ inkscape:export-ydpi="90.000244">
+ <defs
+ id="defs4150" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="23.25"
+ inkscape:cx="4.7741936"
+ inkscape:cy="7.6812068"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ units="px"
+ inkscape:window-width="1280"
+ inkscape:window-height="755"
+ inkscape:window-x="0"
+ inkscape:window-y="1"
+ inkscape:window-maximized="1"
+ inkscape:snap-bbox="true" />
+ <metadata
+ id="metadata4153">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(0,-1036.3622)">
+ <path
+ style="opacity:1;fill:#ffffff;fill-opacity:0;fill-rule:evenodd;stroke:#000000;stroke-width:0.99369001;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="m 11.237265,1036.8591 a 4.2655011,4.1972104 0 0 0 -4.2658922,4.1979 4.2655011,4.1972104 0 0 0 0.025231,0.427 5.3408028,5.2552965 0 0 0 -1.1586611,-0.1299 5.3408028,5.2552965 0 0 0 -5.34109749,5.2556 5.3408028,5.2552965 0 0 0 5.34109749,5.2557 5.3408028,5.2552965 0 0 0 5.3410983,-5.2557 5.3408028,5.2552965 0 0 0 -0.186318,-1.3664 4.2655011,4.1972104 0 0 0 0.244542,0.012 4.2655011,4.1972104 0 0 0 4.26589,-4.198 4.2655011,4.1972104 0 0 0 -4.26589,-4.1979 z"
+ id="path818"
+ inkscape:connector-curvature="0" />
+ <path
+ style="opacity:1;fill:#000000;fill-opacity:0.15686275;fill-rule:evenodd;stroke:none;stroke-width:0.99369001;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="m 11.005859,1038.875 c -4.8895767,1.3679 0.321278,7.2823 2.416016,2.7578 0.450567,-1.4666 -0.923376,-2.9624 -2.416016,-2.7578 z m -5.5078121,4.5 c -5.49629855,0.4756 -2.1892763,9.0394 2.2363281,5.9121 2.667642,-1.8963 1.5392118,-6.8607 -2.2363281,-5.9121 z"
+ id="path836"
+ inkscape:connector-curvature="0" />
+ </g>
+</svg>
diff --git a/platform/macos/docs/guides/For Style Authors.md b/platform/macos/docs/guides/For Style Authors.md
index 4a16066a2a..636cfa7eb7 100644
--- a/platform/macos/docs/guides/For Style Authors.md
+++ b/platform/macos/docs/guides/For Style Authors.md
@@ -96,6 +96,7 @@ the following terms for concepts defined in the style specification:
In the style specification | In the SDK
---------------------------|---------
+bounds | coordinate bounds
filter | predicate
function type | interpolation mode
id | identifier
@@ -136,9 +137,11 @@ In style JSON | In TileJSON | In the SDK
`tiles` | `tiles` | `tileURLTemplates` parameter in `-[MGLTileSource initWithIdentifier:tileURLTemplates:options:]`
`minzoom` | `minzoom` | `MGLTileSourceOptionMinimumZoomLevel`
`maxzoom` | `maxzoom` | `MGLTileSourceOptionMaximumZoomLevel`
+`bounds` | `bounds` | `MGLTileSourceOptionCoordinateBounds`
`tileSize` | — | `MGLTileSourceOptionTileSize`
`attribution` | `attribution` | `MGLTileSourceOptionAttributionHTMLString` (but consider specifying `MGLTileSourceOptionAttributionInfos` instead for improved security)
`scheme` | `scheme` | `MGLTileSourceOptionTileCoordinateSystem`
+`encoding` | – | `MGLTileSourceOptionDEMEncoding`
### Shape sources
@@ -177,6 +180,7 @@ In style JSON | In the SDK
`circle` | `MGLCircleStyleLayer`
`fill` | `MGLFillStyleLayer`
`fill-extrusion` | `MGLFillExtrusionStyleLayer`
+`heatmap` | `MGLHeatmapStyleLayer`
`hillshade` | `MGLHillshadeStyleLayer`
`line` | `MGLLineStyleLayer`
`raster` | `MGLRasterStyleLayer`
diff --git a/platform/macos/jazzy.yml b/platform/macos/jazzy.yml
index 682ccd99ff..b4dccb620f 100644
--- a/platform/macos/jazzy.yml
+++ b/platform/macos/jazzy.yml
@@ -80,6 +80,7 @@ custom_categories:
- MGLCircleStyleLayer
- MGLFillStyleLayer
- MGLFillExtrusionStyleLayer
+ - MGLHeatmapStyleLayer
- MGLHillshadeStyleLayer
- MGLLineStyleLayer
- MGLSymbolStyleLayer
diff --git a/platform/macos/macos.xcodeproj/project.pbxproj b/platform/macos/macos.xcodeproj/project.pbxproj
index 090902fca4..4327670911 100644
--- a/platform/macos/macos.xcodeproj/project.pbxproj
+++ b/platform/macos/macos.xcodeproj/project.pbxproj
@@ -16,6 +16,8 @@
07D9474D1F67441B00E37934 /* MGLAbstractShapeSource_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 07D947471F6741F500E37934 /* MGLAbstractShapeSource_Private.h */; };
07F8E2F71F674C8800F794BB /* MGLComputedShapeSource.h in Headers */ = {isa = PBXBuildFile; fileRef = 07F8E2F41F674C8000F794BB /* MGLComputedShapeSource.h */; settings = {ATTRIBUTES = (Public, ); }; };
07F8E2F81F674C9000F794BB /* MGLComputedShapeSource.mm in Sources */ = {isa = PBXBuildFile; fileRef = 07F8E2F51F674C8000F794BB /* MGLComputedShapeSource.mm */; };
+ 170A82BF201BDD1B00943087 /* MGLHeatmapStyleLayerTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 170A82BE201BDD1B00943087 /* MGLHeatmapStyleLayerTests.mm */; };
+ 170A82C4201FB6EC00943087 /* MGLHeatmapColorTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 170A82C2201FAFF800943087 /* MGLHeatmapColorTests.mm */; };
1753ED401E53CE6100A9FD90 /* MGLConversion.h in Headers */ = {isa = PBXBuildFile; fileRef = 1753ED3F1E53CE5200A9FD90 /* MGLConversion.h */; };
1F7454A31ECFB00300021D39 /* MGLLight_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 1F7454A01ECFB00300021D39 /* MGLLight_Private.h */; };
1F7454A41ECFB00300021D39 /* MGLLight.h in Headers */ = {isa = PBXBuildFile; fileRef = 1F7454A11ECFB00300021D39 /* MGLLight.h */; settings = {ATTRIBUTES = (Public, ); }; };
@@ -85,6 +87,9 @@
55D120A31F7906E6004B6D81 /* libmbgl-filesource.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 55D120A41F7906E6004B6D81 /* libmbgl-filesource.a */; };
55D120A51F790A0C004B6D81 /* libmbgl-filesource.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 55D120A41F7906E6004B6D81 /* libmbgl-filesource.a */; };
55E2AD111E5B0A6900E8C587 /* MGLOfflineStorageTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 55E2AD101E5B0A6900E8C587 /* MGLOfflineStorageTests.mm */; };
+ 89462399200D199100DA8EF2 /* heatmap.json in Resources */ = {isa = PBXBuildFile; fileRef = 89462398200D199100DA8EF2 /* heatmap.json */; };
+ 8946239D200E744800DA8EF2 /* MGLHeatmapStyleLayer.h in Headers */ = {isa = PBXBuildFile; fileRef = 8946239A200E73CA00DA8EF2 /* MGLHeatmapStyleLayer.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 894623A0200E748000DA8EF2 /* MGLHeatmapStyleLayer.mm in Sources */ = {isa = PBXBuildFile; fileRef = 8946239B200E73CA00DA8EF2 /* MGLHeatmapStyleLayer.mm */; };
92092EF01F5EB10E00AF5130 /* MGLMapSnapshotter.h in Headers */ = {isa = PBXBuildFile; fileRef = 92092EEE1F5EB10E00AF5130 /* MGLMapSnapshotter.h */; settings = {ATTRIBUTES = (Public, ); }; };
92092EF11F5EB10E00AF5130 /* MGLMapSnapshotter.mm in Sources */ = {isa = PBXBuildFile; fileRef = 92092EEF1F5EB10E00AF5130 /* MGLMapSnapshotter.mm */; };
920A3E591E6F859D00C16EFC /* MGLSourceQueryTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 920A3E581E6F859D00C16EFC /* MGLSourceQueryTests.m */; };
@@ -298,6 +303,8 @@
07D947491F6741F500E37934 /* MGLAbstractShapeSource.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLAbstractShapeSource.mm; sourceTree = "<group>"; };
07F8E2F41F674C8000F794BB /* MGLComputedShapeSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLComputedShapeSource.h; sourceTree = "<group>"; };
07F8E2F51F674C8000F794BB /* MGLComputedShapeSource.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLComputedShapeSource.mm; sourceTree = "<group>"; };
+ 170A82BE201BDD1B00943087 /* MGLHeatmapStyleLayerTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLHeatmapStyleLayerTests.mm; sourceTree = "<group>"; };
+ 170A82C2201FAFF800943087 /* MGLHeatmapColorTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLHeatmapColorTests.mm; sourceTree = "<group>"; };
1753ED3F1E53CE5200A9FD90 /* MGLConversion.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLConversion.h; sourceTree = "<group>"; };
1F7454A01ECFB00300021D39 /* MGLLight_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLLight_Private.h; sourceTree = "<group>"; };
1F7454A11ECFB00300021D39 /* MGLLight.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLLight.h; sourceTree = "<group>"; };
@@ -369,6 +376,9 @@
55D9B4B01D005D3900C1CCE2 /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; };
55E2AD101E5B0A6900E8C587 /* MGLOfflineStorageTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLOfflineStorageTests.mm; path = ../../darwin/test/MGLOfflineStorageTests.mm; sourceTree = "<group>"; };
55FE0E8D1D100A0900FD240B /* config.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = config.xcconfig; path = ../../build/macos/config.xcconfig; sourceTree = "<group>"; };
+ 89462398200D199100DA8EF2 /* heatmap.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = heatmap.json; sourceTree = "<group>"; };
+ 8946239A200E73CA00DA8EF2 /* MGLHeatmapStyleLayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLHeatmapStyleLayer.h; sourceTree = "<group>"; };
+ 8946239B200E73CA00DA8EF2 /* MGLHeatmapStyleLayer.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLHeatmapStyleLayer.mm; sourceTree = "<group>"; };
92092EEE1F5EB10E00AF5130 /* MGLMapSnapshotter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLMapSnapshotter.h; sourceTree = "<group>"; };
92092EEF1F5EB10E00AF5130 /* MGLMapSnapshotter.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLMapSnapshotter.mm; sourceTree = "<group>"; };
920A3E581E6F859D00C16EFC /* MGLSourceQueryTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MGLSourceQueryTests.m; sourceTree = "<group>"; };
@@ -672,6 +682,8 @@
35602BF91D3EA99F0050646F /* MGLFillStyleLayer.mm */,
35602BFD1D3EA9B40050646F /* MGLForegroundStyleLayer.h */,
35602BFE1D3EA9B40050646F /* MGLForegroundStyleLayer.mm */,
+ 8946239A200E73CA00DA8EF2 /* MGLHeatmapStyleLayer.h */,
+ 8946239B200E73CA00DA8EF2 /* MGLHeatmapStyleLayer.mm */,
DAF25714201901C200367EF5 /* MGLHillshadeStyleLayer.h */,
DAF25713201901C100367EF5 /* MGLHillshadeStyleLayer.mm */,
DA8F25891D51CA540010E6B5 /* MGLLineStyleLayer.h */,
@@ -825,6 +837,7 @@
DA839EA61CC2E3400062CAFB /* Info.plist */,
96E027331E57C9A7004B8E66 /* Localizable.strings */,
DA839E981CC2E3400062CAFB /* Supporting Files */,
+ 89462398200D199100DA8EF2 /* heatmap.json */,
);
name = "Demo App";
path = app;
@@ -875,6 +888,8 @@
DA8F257C1D51C5F40010E6B5 /* Layers */ = {
isa = PBXGroup;
children = (
+ 170A82C2201FAFF800943087 /* MGLHeatmapColorTests.mm */,
+ 170A82BE201BDD1B00943087 /* MGLHeatmapStyleLayerTests.mm */,
40E1601A1DF216E6005EA6D9 /* MGLStyleLayerTests.h */,
40E1601B1DF216E6005EA6D9 /* MGLStyleLayerTests.m */,
DA2207BA1DC076930002F84D /* test-Bridging-Header.h */,
@@ -1232,6 +1247,7 @@
359819591E02F611008FC139 /* NSCoder+MGLAdditions.h in Headers */,
DAE6C38E1CC31E2A00DB3429 /* MGLOfflineStorage_Private.h in Headers */,
DA87A9A01DC9DC6200810D09 /* MGLValueEvaluator.h in Headers */,
+ 8946239D200E744800DA8EF2 /* MGLHeatmapStyleLayer.h in Headers */,
DAE6C3601CC31E0400DB3429 /* MGLOfflineRegion.h in Headers */,
DAE6C3681CC31E0400DB3429 /* MGLTilePyramidOfflineRegion.h in Headers */,
DA35A2CF1CCAAED300E826B2 /* NSValue+MGLAdditions.h in Headers */,
@@ -1422,6 +1438,7 @@
DA839EA01CC2E3400062CAFB /* MapDocument.xib in Resources */,
353BAEF81D6463B8009A8DA9 /* amsterdam.geojson in Resources */,
96E027311E57C9A7004B8E66 /* Localizable.strings in Resources */,
+ 89462399200D199100DA8EF2 /* heatmap.json in Resources */,
DA839EA51CC2E3400062CAFB /* MainMenu.xib in Resources */,
DA5589771D320C41006B7F64 /* wms.json in Resources */,
DAE6C2E21CC304F900DB3429 /* Credits.rtf in Resources */,
@@ -1503,6 +1520,7 @@
07D9474B1F6743F000E37934 /* MGLAbstractShapeSource.mm in Sources */,
DAE6C3941CC31E2A00DB3429 /* MGLStyle.mm in Sources */,
DAE6C3871CC31E2A00DB3429 /* MGLGeometry.mm in Sources */,
+ 894623A0200E748000DA8EF2 /* MGLHeatmapStyleLayer.mm in Sources */,
3527428E1D4C24AB00A1ECE6 /* MGLCircleStyleLayer.mm in Sources */,
35602C011D3EA9B40050646F /* MGLForegroundStyleLayer.mm in Sources */,
408AA86A1DAEEE5D00022900 /* NSDictionary+MGLAdditions.mm in Sources */,
@@ -1565,11 +1583,13 @@
DAE6C3D21CC34C9900DB3429 /* MGLGeometryTests.mm in Sources */,
DA87A9A41DCACC5000810D09 /* MGLSymbolStyleLayerTests.mm in Sources */,
40E1601D1DF217D6005EA6D9 /* MGLStyleLayerTests.m in Sources */,
+ 170A82BF201BDD1B00943087 /* MGLHeatmapStyleLayerTests.mm in Sources */,
1F95931B1E6DE2B600D5B294 /* MGLNSDateAdditionsTests.mm in Sources */,
DAF25721201902C100367EF5 /* MGLHillshadeStyleLayerTests.mm in Sources */,
DA87A9A61DCACC5000810D09 /* MGLCircleStyleLayerTests.mm in Sources */,
DA87A99E1DC9DC2100810D09 /* MGLPredicateTests.mm in Sources */,
DD58A4C91D822C6700E1F038 /* MGLExpressionTests.mm in Sources */,
+ 170A82C4201FB6EC00943087 /* MGLHeatmapColorTests.mm in Sources */,
4031ACFC1E9EB3C100A3EA26 /* MGLMapViewDelegateIntegrationTests.swift in Sources */,
4031AD031E9FD6AA00A3EA26 /* MGLSDKTestHelpers.swift in Sources */,
DA87A9A71DCACC5000810D09 /* MGLBackgroundStyleLayerTests.mm in Sources */,
diff --git a/platform/macos/src/Mapbox.h b/platform/macos/src/Mapbox.h
index 7ea9a9dd1d..0e4b546cf7 100644
--- a/platform/macos/src/Mapbox.h
+++ b/platform/macos/src/Mapbox.h
@@ -44,6 +44,7 @@ FOUNDATION_EXPORT MGL_EXPORT const unsigned char MapboxVersionString[];
#import "MGLRasterStyleLayer.h"
#import "MGLCircleStyleLayer.h"
#import "MGLBackgroundStyleLayer.h"
+#import "MGLHeatmapStyleLayer.h"
#import "MGLHillshadeStyleLayer.h"
#import "MGLOpenGLStyleLayer.h"
#import "MGLSource.h"
diff --git a/platform/node/src/node_expression.cpp b/platform/node/src/node_expression.cpp
index 8958d5c6c7..84515060a3 100644
--- a/platform/node/src/node_expression.cpp
+++ b/platform/node/src/node_expression.cpp
@@ -1,5 +1,6 @@
#include "node_conversion.hpp"
#include "node_expression.hpp"
+#include "node_feature.hpp"
#include <mbgl/style/expression/parsing_context.hpp>
#include <mbgl/style/expression/is_constant.hpp>
@@ -24,6 +25,8 @@ void NodeExpression::Init(v8::Local<v8::Object> target) {
Nan::SetPrototypeMethod(tpl, "isFeatureConstant", IsFeatureConstant);
Nan::SetPrototypeMethod(tpl, "isZoomConstant", IsZoomConstant);
+ Nan::SetPrototypeMethod(tpl, "serialize", Serialize);
+
Nan::SetMethod(tpl, "parse", Parse);
constructor.Reset(tpl->GetFunction()); // what is this doing?
@@ -39,31 +42,31 @@ type::Type parseType(v8::Local<v8::Object> type) {
{"color", type::Color},
{"value", type::Value}
};
-
+
v8::Local<v8::Value> v8kind = Nan::Get(type, Nan::New("kind").ToLocalChecked()).ToLocalChecked();
std::string kind(*v8::String::Utf8Value(v8kind));
-
+
if (kind == "array") {
type::Type itemType = parseType(Nan::Get(type, Nan::New("itemType").ToLocalChecked()).ToLocalChecked()->ToObject());
mbgl::optional<std::size_t> N;
-
+
v8::Local<v8::String> Nkey = Nan::New("N").ToLocalChecked();
if (Nan::Has(type, Nkey).FromMaybe(false)) {
N = Nan::Get(type, Nkey).ToLocalChecked()->ToInt32()->Value();
}
return type::Array(itemType, N);
}
-
+
return types[kind];
}
void NodeExpression::Parse(const Nan::FunctionCallbackInfo<v8::Value>& info) {
v8::Local<v8::Function> cons = Nan::New(constructor);
-
+
if (info.Length() < 1 || info[0]->IsUndefined()) {
return Nan::ThrowTypeError("Requires a JSON style expression argument.");
}
-
+
mbgl::optional<type::Type> expected;
if (info.Length() > 1 && info[1]->IsObject()) {
expected = parseType(info[1]->ToObject());
@@ -84,7 +87,7 @@ void NodeExpression::Parse(const Nan::FunctionCallbackInfo<v8::Value>& info) {
info.GetReturnValue().Set(wrapped);
return;
}
-
+
v8::Local<v8::Array> result = Nan::New<v8::Array>();
for (std::size_t i = 0; i < ctx.getErrors().size(); i++) {
const auto& error = ctx.getErrors()[i];
@@ -140,7 +143,7 @@ struct ToValue {
}
return scope.Escape(result);
}
-
+
v8::Local<v8::Value> operator()(const mbgl::Color& color) {
return operator()(std::vector<Value> {
static_cast<double>(color.r),
@@ -227,4 +230,12 @@ void NodeExpression::IsZoomConstant(const Nan::FunctionCallbackInfo<v8::Value>&
info.GetReturnValue().Set(Nan::New(isZoomConstant(*expression)));
}
+void NodeExpression::Serialize(const Nan::FunctionCallbackInfo<v8::Value>& info) {
+ NodeExpression* nodeExpr = ObjectWrap::Unwrap<NodeExpression>(info.Holder());
+ const std::unique_ptr<Expression>& expression = nodeExpr->expression;
+
+ const mbgl::Value serialized = expression->serialize();
+ info.GetReturnValue().Set(toJS(serialized));
+}
+
} // namespace node_mbgl
diff --git a/platform/node/src/node_expression.hpp b/platform/node/src/node_expression.hpp
index 7af5b7ab51..05af217bde 100644
--- a/platform/node/src/node_expression.hpp
+++ b/platform/node/src/node_expression.hpp
@@ -32,6 +32,9 @@ private:
static void GetType(const Nan::FunctionCallbackInfo<v8::Value>&);
static void IsFeatureConstant(const Nan::FunctionCallbackInfo<v8::Value>&);
static void IsZoomConstant(const Nan::FunctionCallbackInfo<v8::Value>&);
+
+ static void Serialize(const Nan::FunctionCallbackInfo<v8::Value>&);
+
static Nan::Persistent<v8::Function> constructor;
std::unique_ptr<Expression> expression;
diff --git a/platform/node/src/node_map.cpp b/platform/node/src/node_map.cpp
index 3e66aaa789..0fe69e8ac9 100644
--- a/platform/node/src/node_map.cpp
+++ b/platform/node/src/node_map.cpp
@@ -15,6 +15,7 @@
#include <mbgl/style/layers/circle_layer.hpp>
#include <mbgl/style/layers/fill_layer.hpp>
#include <mbgl/style/layers/fill_extrusion_layer.hpp>
+#include <mbgl/style/layers/heatmap_layer.hpp>
#include <mbgl/style/layers/hillshade_layer.hpp>
#include <mbgl/style/layers/line_layer.hpp>
#include <mbgl/style/layers/raster_layer.hpp>
@@ -1153,6 +1154,10 @@ NodeMap::~NodeMap() {
std::unique_ptr<mbgl::AsyncRequest> NodeMap::request(const mbgl::Resource& resource, mbgl::FileSource::Callback callback_) {
Nan::HandleScope scope;
+ // Because this method may be called while this NodeMap is already eligible for garbage collection,
+ // we need to explicitly hold onto our own handle here so that GC during a v8 call doesn't destroy
+ // *this while we're still executing code.
+ handle();
v8::Local<v8::Value> argv[] = {
Nan::New<v8::External>(this),
diff --git a/platform/node/test/expression.test.js b/platform/node/test/expression.test.js
index aac039ce18..ffd1c68ff2 100644
--- a/platform/node/test/expression.test.js
+++ b/platform/node/test/expression.test.js
@@ -32,40 +32,54 @@ function getExpectedType(spec) {
suite.run('native', {ignores: ignores, tests: tests}, (fixture) => {
const compiled = {};
+ const recompiled = {};
const result = {
- compiled
+ compiled,
+ recompiled
};
const spec = fixture.propertySpec || {};
const expression = mbgl.Expression.parse(fixture.expression, getExpectedType(spec));
- if (expression instanceof mbgl.Expression) {
- compiled.result = 'success';
- compiled.isFeatureConstant = expression.isFeatureConstant();
- compiled.isZoomConstant = expression.isZoomConstant();
- compiled.type = expression.getType();
+ const evaluateExpression = (expression, compilationResult) => {
+ if (expression instanceof mbgl.Expression) {
+ compilationResult.result = 'success';
+ compilationResult.isFeatureConstant = expression.isFeatureConstant();
+ compilationResult.isZoomConstant = expression.isZoomConstant();
+ compilationResult.type = expression.getType();
- const evaluate = fixture.inputs || [];
- const evaluateResults = [];
- for (const input of evaluate) {
- const feature = Object.assign({
- type: 'Feature',
- properties: {},
- geometry: { type: 'Point', coordinates: [0, 0] }
- }, input[1])
+ const evaluate = fixture.inputs || [];
+ const evaluateResults = [];
+ for (const input of evaluate) {
+ const feature = Object.assign({
+ type: 'Feature',
+ properties: {},
+ geometry: { type: 'Point', coordinates: [0, 0] }
+ }, input[1])
- const output = expression.evaluate(input[0], feature);
- evaluateResults.push(output);
- }
+ const output = expression.evaluate(input[0], feature);
+ evaluateResults.push(output);
+ }
- if (fixture.inputs) {
- result.outputs = evaluateResults;
+ if (fixture.inputs) {
+ return evaluateResults;
+ }
+ } else {
+ compilationResult.result = 'error';
+ compilationResult.errors = expression;
}
- } else {
- compiled.result = 'error';
- compiled.errors = expression;
+ }
+
+ result.outputs = evaluateExpression(expression, compiled);
+ if (expression instanceof mbgl.Expression) {
+ result.serialized = expression.serialize();
+ const recompiledExpression = mbgl.Expression.parse(result.serialized, getExpectedType(spec));
+ result.roundTripOutputs = evaluateExpression(recompiledExpression, recompiled);
+ // Type is allowed to change through serialization
+ // (eg "array" -> "array<number, 3>")
+ // Override the round-tripped type here so that the equality check passes
+ recompiled.type = compiled.type;
}
return result;
});
-
diff --git a/platform/node/test/ignores.json b/platform/node/test/ignores.json
index ab187a86b3..42751da30f 100644
--- a/platform/node/test/ignores.json
+++ b/platform/node/test/ignores.json
@@ -9,6 +9,7 @@
"render-tests/background-color/transition": "https://github.com/mapbox/mapbox-gl-native/issues/10619",
"render-tests/debug/collision": "https://github.com/mapbox/mapbox-gl-native/issues/3841",
"render-tests/debug/collision-lines": "https://github.com/mapbox/mapbox-gl-native/issues/10412",
+ "render-tests/debug/collision-lines-overscaled": "https://github.com/mapbox/mapbox-gl-native/issues/10412",
"render-tests/debug/collision-lines-pitched": "https://github.com/mapbox/mapbox-gl-native/issues/10412",
"render-tests/debug/collision-pitched-wrapped": "https://github.com/mapbox/mapbox-gl-native/issues/10412",
"render-tests/debug/tile": "https://github.com/mapbox/mapbox-gl-native/issues/3841",
@@ -22,24 +23,6 @@
"render-tests/fill-extrusion-pattern/opacity": "https://github.com/mapbox/mapbox-gl-js/issues/3327",
"render-tests/geojson/inline-linestring-fill": "current behavior is arbitrary",
"render-tests/geojson/inline-polygon-symbol": "behavior needs reconciliation with gl-js",
- "render-tests/heatmap-color/default": "https://github.com/mapbox/mapbox-gl-native/issues/10146",
- "render-tests/heatmap-color/expression": "https://github.com/mapbox/mapbox-gl-native/issues/10146",
- "render-tests/heatmap-color/function": "https://github.com/mapbox/mapbox-gl-native/issues/10146",
- "render-tests/heatmap-intensity/default": "https://github.com/mapbox/mapbox-gl-native/issues/10146",
- "render-tests/heatmap-intensity/function": "https://github.com/mapbox/mapbox-gl-native/issues/10146",
- "render-tests/heatmap-intensity/literal": "https://github.com/mapbox/mapbox-gl-native/issues/10146",
- "render-tests/heatmap-opacity/default": "https://github.com/mapbox/mapbox-gl-native/issues/10146",
- "render-tests/heatmap-opacity/function": "https://github.com/mapbox/mapbox-gl-native/issues/10146",
- "render-tests/heatmap-opacity/literal": "https://github.com/mapbox/mapbox-gl-native/issues/10146",
- "render-tests/heatmap-radius/antimeridian": "https://github.com/mapbox/mapbox-gl-native/issues/10146",
- "render-tests/heatmap-radius/data-expression": "https://github.com/mapbox/mapbox-gl-native/issues/10146",
- "render-tests/heatmap-radius/default": "https://github.com/mapbox/mapbox-gl-native/issues/10146",
- "render-tests/heatmap-radius/function": "https://github.com/mapbox/mapbox-gl-native/issues/10146",
- "render-tests/heatmap-radius/literal": "https://github.com/mapbox/mapbox-gl-native/issues/10146",
- "render-tests/heatmap-radius/pitch30": "https://github.com/mapbox/mapbox-gl-native/issues/10146",
- "render-tests/heatmap-weight/default": "https://github.com/mapbox/mapbox-gl-native/issues/10146",
- "render-tests/heatmap-weight/identity-property-function": "https://github.com/mapbox/mapbox-gl-native/issues/10146",
- "render-tests/heatmap-weight/literal": "https://github.com/mapbox/mapbox-gl-native/issues/10146",
"render-tests/mixed-zoom/z10-z11": "https://github.com/mapbox/mapbox-gl-native/issues/10397",
"render-tests/raster-masking/overlapping-zoom": "https://github.com/mapbox/mapbox-gl-native/issues/10195",
"render-tests/regressions/mapbox-gl-js#2305": "https://github.com/mapbox/mapbox-gl-native/issues/6927",
@@ -84,6 +67,10 @@
"render-tests/combinations/line-translucent--heatmap-translucent": "https://github.com/mapbox/mapbox-gl-native/issues/10146",
"render-tests/combinations/raster-translucent--heatmap-translucent": "https://github.com/mapbox/mapbox-gl-native/issues/10146",
"render-tests/combinations/symbol-translucent--heatmap-translucent": "https://github.com/mapbox/mapbox-gl-native/issues/10146",
+ "render-tests/combinations/background-opaque--hillshade-translucent": "https://github.com/mapbox/mapbox-gl-native/pull/10642",
+ "render-tests/combinations/background-translucent--hillshade-translucent": "https://github.com/mapbox/mapbox-gl-native/pull/10642",
+ "render-tests/combinations/circle-translucent--hillshade-translucent": "https://github.com/mapbox/mapbox-gl-native/pull/10642",
+ "render-tests/combinations/fill-extrusion-translucent--hillshade-translucent": "https://github.com/mapbox/mapbox-gl-native/pull/10642",
"render-tests/combinations/background-opaque--fill-extrusion-translucent": "needs investigation",
"render-tests/combinations/background-translucent--fill-extrusion-translucent": "needs investigation",
"render-tests/combinations/circle-translucent--fill-extrusion-translucent": "needs investigation",
diff --git a/platform/qt/README.md b/platform/qt/README.md
index 4d2d887828..018f8823b6 100644
--- a/platform/qt/README.md
+++ b/platform/qt/README.md
@@ -1,4 +1,4 @@
-# Mapbox Qt SDK
+# Mapbox Maps SDK for Qt
[![Circle CI build status](https://circleci.com/gh/mapbox/mapbox-gl-native.svg?style=shield)](https://circleci.com/gh/mapbox/workflows/mapbox-gl-native/tree/master) [![AppVeyor CI build status](https://ci.appveyor.com/api/projects/status/3q12kbcooc6df8uc?svg=true)](https://ci.appveyor.com/project/Mapbox/mapbox-gl-native)
@@ -9,7 +9,7 @@ available in the Qt SDK since Qt 5.9. Use the [Qt bugtracker](https://bugreports
to the plugin and this GitHub repository for bugs related to Mapbox GL Native and the Qt bindings.
You should build this repository if you want to develop/contribute using the low level Qt C++ bindings or
-want to contribute to the Mapbox GL Native projectusing the Qt port for debugging.
+want to contribute to the Mapbox GL Native project using the Qt port for debugging.
See the Mapbox Qt landing page for more details: https://www.mapbox.com/qt/
diff --git a/platform/qt/app/mapwindow.cpp b/platform/qt/app/mapwindow.cpp
index fc346cdcde..f6d5473192 100644
--- a/platform/qt/app/mapwindow.cpp
+++ b/platform/qt/app/mapwindow.cpp
@@ -442,10 +442,9 @@ void MapWindow::initializeGL()
void MapWindow::paintGL()
{
m_frameDraws++;
- m_map->resize(size(), size() * pixelRatio());
+ m_map->resize(size());
#if QT_VERSION >= 0x050400
- // When we're using QOpenGLWidget, we need to tell Mapbox GL about the framebuffer we're using.
- m_map->setFramebufferObject(defaultFramebufferObject());
+ m_map->setFramebufferObject(defaultFramebufferObject(), size() * pixelRatio());
#endif
m_map->render();
}
diff --git a/platform/qt/config.qdocconf b/platform/qt/config.qdocconf
index d6f0edbd32..9d3b8a7ef4 100644
--- a/platform/qt/config.qdocconf
+++ b/platform/qt/config.qdocconf
@@ -7,7 +7,7 @@ include($QT_INSTALL_DOCS/global/qt-html-templates-online.qdocconf)
Cpp.ignoretokens = Q_MAPBOXGL_EXPORT
project = QMapboxGL
-description = Mapbox Qt SDK
+description = Mapbox Maps SDK for Qt
language = Cpp
outputdir = docs
diff --git a/platform/qt/include/qmapboxgl.hpp b/platform/qt/include/qmapboxgl.hpp
index 8b319b0453..bc18eaba59 100644
--- a/platform/qt/include/qmapboxgl.hpp
+++ b/platform/qt/include/qmapboxgl.hpp
@@ -26,6 +26,11 @@ public:
SharedGLContext
};
+ enum MapMode {
+ Continuous = 0,
+ Static
+ };
+
enum ConstrainMode {
NoConstrain = 0,
ConstrainHeightOnly,
@@ -40,6 +45,9 @@ public:
GLContextMode contextMode() const;
void setContextMode(GLContextMode);
+ MapMode mapMode() const;
+ void setMapMode(MapMode);
+
ConstrainMode constrainMode() const;
void setConstrainMode(ConstrainMode);
@@ -66,6 +74,7 @@ public:
private:
GLContextMode m_contextMode;
+ MapMode m_mapMode;
ConstrainMode m_constrainMode;
ViewportMode m_viewportMode;
@@ -191,8 +200,7 @@ public:
void scaleBy(double scale, const QPointF &center = QPointF());
void rotateBy(const QPointF &first, const QPointF &second);
- void resize(const QSize &size, const QSize &framebufferSize);
- void setFramebufferObject(quint32 fbo);
+ void resize(const QSize &size);
double metersPerPixelAtLatitude(double latitude, double zoom) const;
QMapbox::ProjectedMeters projectedMetersForCoordinate(const QMapbox::Coordinate &) const;
@@ -226,15 +234,27 @@ public:
void setFilter(const QString &layer, const QVariant &filter);
+ // When rendering on a different thread,
+ // should be called on the render thread.
+ void createRenderer();
+ void destroyRenderer();
+ void setFramebufferObject(quint32 fbo, const QSize &size);
+
public slots:
void render();
void connectionEstablished();
+ // Commit changes, load all the resources
+ // and renders the map when completed.
+ void startStaticRender();
+
signals:
void needsRendering();
void mapChanged(QMapboxGL::MapChange);
void copyrightsChanged(const QString &copyrightsHtml);
+ void staticRenderFinished(const QString &error);
+
private:
Q_DISABLE_COPY(QMapboxGL)
diff --git a/platform/qt/qt.cmake b/platform/qt/qt.cmake
index aaae199650..d1d597bda6 100644
--- a/platform/qt/qt.cmake
+++ b/platform/qt/qt.cmake
@@ -61,8 +61,12 @@ add_library(qmapboxgl SHARED
platform/qt/src/qmapbox.cpp
platform/qt/src/qmapboxgl.cpp
platform/qt/src/qmapboxgl_p.hpp
- platform/qt/src/qmapboxgl_renderer_frontend_p.hpp
- platform/qt/src/qmapboxgl_renderer_frontend_p.cpp
+ platform/qt/src/qmapboxgl_map_observer.cpp
+ platform/qt/src/qmapboxgl_map_observer.hpp
+ platform/qt/src/qmapboxgl_map_renderer.cpp
+ platform/qt/src/qmapboxgl_map_renderer.hpp
+ platform/qt/src/qmapboxgl_renderer_backend.hpp
+ platform/qt/src/qmapboxgl_renderer_backend.cpp
platform/default/mbgl/util/default_styles.hpp
)
diff --git a/platform/qt/src/qmapbox.cpp b/platform/qt/src/qmapbox.cpp
index ec76ebfe53..1386a4b7aa 100644
--- a/platform/qt/src/qmapbox.cpp
+++ b/platform/qt/src/qmapbox.cpp
@@ -24,7 +24,7 @@ namespace QMapbox {
/*!
\namespace QMapbox
- \inmodule Mapbox Qt SDK
+ \inmodule Mapbox Maps SDK for Qt
Contains miscellaneous Mapbox bindings used throughout QMapboxGL.
*/
@@ -74,7 +74,7 @@ namespace QMapbox {
/*!
\class QMapbox::Feature
- \inmodule Mapbox Qt SDK
+ \inmodule Mapbox Maps SDK for Qt
Represents \l {https://www.mapbox.com/help/define-features/}{map features}
via its \a type (PointType, LineStringType or PolygonType), \a geometry, \a
@@ -94,7 +94,7 @@ namespace QMapbox {
/*!
\class QMapbox::ShapeAnnotationGeometry
- \inmodule Mapbox Qt SDK
+ \inmodule Mapbox Maps SDK for Qt
Represents a shape annotation geometry.
*/
@@ -113,7 +113,7 @@ namespace QMapbox {
/*!
\class QMapbox::SymbolAnnotation
- \inmodule Mapbox Qt SDK
+ \inmodule Mapbox Maps SDK for Qt
A symbol annotation comprises of its geometry and an icon identifier.
*/
@@ -121,7 +121,7 @@ namespace QMapbox {
/*!
\class QMapbox::LineAnnotation
- \inmodule Mapbox Qt SDK
+ \inmodule Mapbox Maps SDK for Qt
Represents a line annotation object, along with its properties.
@@ -131,7 +131,7 @@ namespace QMapbox {
/*!
\class QMapbox::FillAnnotation
- \inmodule Mapbox Qt SDK
+ \inmodule Mapbox Maps SDK for Qt
Represents a fill annotation object, along with its properties.
@@ -195,7 +195,7 @@ namespace QMapbox {
/*!
\class QMapbox::CustomLayerRenderParameters
- \inmodule Mapbox Qt SDK
+ \inmodule Mapbox Maps SDK for Qt
QMapbox::CustomLayerRenderParameters provides the data passed on each render
pass for a custom layer.
diff --git a/platform/qt/src/qmapboxgl.cpp b/platform/qt/src/qmapboxgl.cpp
index 2675d87862..982f4b3b35 100644
--- a/platform/qt/src/qmapboxgl.cpp
+++ b/platform/qt/src/qmapboxgl.cpp
@@ -1,6 +1,8 @@
#include "qmapboxgl.hpp"
#include "qmapboxgl_p.hpp"
-#include "qmapboxgl_renderer_frontend_p.hpp"
+
+#include "qmapboxgl_map_observer.hpp"
+#include "qmapboxgl_renderer_observer.hpp"
#include "qt_conversion.hpp"
#include "qt_geojson.hpp"
@@ -43,11 +45,8 @@
#if QT_VERSION >= 0x050000
#include <QGuiApplication>
-#include <QWindow>
-#include <QOpenGLContext>
#else
#include <QCoreApplication>
-#include <QGLContext>
#endif
#include <QDebug>
@@ -65,6 +64,10 @@ using namespace QMapbox;
static_assert(mbgl::underlying_type(QMapboxGLSettings::UniqueGLContext) == mbgl::underlying_type(mbgl::GLContextMode::Unique), "error");
static_assert(mbgl::underlying_type(QMapboxGLSettings::SharedGLContext) == mbgl::underlying_type(mbgl::GLContextMode::Shared), "error");
+// mbgl::MapMode
+static_assert(mbgl::underlying_type(QMapboxGLSettings::Continuous) == mbgl::underlying_type(mbgl::MapMode::Continuous), "error");
+static_assert(mbgl::underlying_type(QMapboxGLSettings::Static) == mbgl::underlying_type(mbgl::MapMode::Static), "error");
+
// mbgl::ConstrainMode
static_assert(mbgl::underlying_type(QMapboxGLSettings::NoConstrain) == mbgl::underlying_type(mbgl::ConstrainMode::None), "error");
static_assert(mbgl::underlying_type(QMapboxGLSettings::ConstrainHeightOnly) == mbgl::underlying_type(mbgl::ConstrainMode::HeightOnly), "error");
@@ -133,7 +136,7 @@ std::unique_ptr<mbgl::style::Image> toStyleImage(const QString &id, const QImage
\class QMapboxGLSettings
\brief The QMapboxGLSettings class stores the initial configuration for QMapboxGL.
- \inmodule Mapbox Qt SDK
+ \inmodule Mapbox Maps SDK for Qt
QMapboxGLSettings is used to configure QMapboxGL at the moment of its creation.
Once created, the QMapboxGLSettings of a QMapboxGL can no longer be changed.
@@ -165,6 +168,27 @@ std::unique_ptr<mbgl::style::Image> toStyleImage(const QString &id, const QImage
*/
/*!
+ \enum QMapboxGLSettings::MapMode
+
+ This enum sets the map rendering mode
+
+ \value Continuous The map will render as data arrives from the network and
+ react immediately to state changes.
+
+ This is the default mode and the preferred when the map is intended to be
+ interactive.
+
+ \value Static The map will no longer react to state changes and will only
+ be rendered when QMapboxGL::startStaticRender is called. After all the
+ resources are loaded, the QMapboxGL::staticRenderFinished signal is emitted.
+
+ This mode is useful for taking a snapshot of the finished rendering result
+ of the map into a QImage.
+
+ \sa mapMode()
+*/
+
+/*!
\enum QMapboxGLSettings::ConstrainMode
This enum determines if the map wraps.
@@ -200,6 +224,7 @@ std::unique_ptr<mbgl::style::Image> toStyleImage(const QString &id, const QImage
*/
QMapboxGLSettings::QMapboxGLSettings()
: m_contextMode(QMapboxGLSettings::SharedGLContext)
+ , m_mapMode(QMapboxGLSettings::Continuous)
, m_constrainMode(QMapboxGLSettings::ConstrainHeightOnly)
, m_viewportMode(QMapboxGLSettings::DefaultViewport)
, m_cacheMaximumSize(mbgl::util::DEFAULT_MAX_CACHE_SIZE)
@@ -230,6 +255,31 @@ void QMapboxGLSettings::setContextMode(GLContextMode mode)
}
/*!
+ Returns the map mode. Static mode will emit a signal for
+ rendering a map only when the map is fully loaded.
+ Animations like style transitions and labels fading won't
+ be seen.
+
+ The Continuous mode will emit the signal for every new
+ change on the map and it is usually what you expect for
+ a interactive map.
+
+ By default, it is set to QMapboxGLSettings::Continuous.
+*/
+QMapboxGLSettings::MapMode QMapboxGLSettings::mapMode() const
+{
+ return m_mapMode;
+}
+
+/*!
+ Sets the map \a mode.
+*/
+void QMapboxGLSettings::setMapMode(MapMode mode)
+{
+ m_mapMode = mode;
+}
+
+/*!
Returns the constrain mode. This is used to limit the map to wrap
around the globe horizontally.
@@ -404,7 +454,7 @@ void QMapboxGLSettings::setResourceTransform(const std::function<std::string(con
\class QMapboxGL
\brief The QMapboxGL class is a Qt wrapper for the Mapbox GL Native engine.
- \inmodule Mapbox Qt SDK
+ \inmodule Mapbox Maps SDK for Qt
QMapboxGL is a Qt friendly version the Mapbox GL Native engine using Qt types
and deep integration with Qt event loop. QMapboxGL relies as much as possible
@@ -1048,32 +1098,17 @@ void QMapboxGL::rotateBy(const QPointF &first, const QPointF &second)
}
/*!
- Resize the map to \a size and scale to fit at \a framebufferSize. For
- high DPI screens, the size will be smaller than the \a framebufferSize.
-
- This fallowing example will double the pixel density of the map for
- a given \c size:
-
- \code
- map->resize(size / 2, size);
- \endcode
+ Resize the map to \a size_ and scale to fit at the framebuffer. For
+ high DPI screens, the size will be smaller than the framebuffer.
*/
-void QMapboxGL::resize(const QSize& size, const QSize& framebufferSize)
+void QMapboxGL::resize(const QSize& size_)
{
- if (d_ptr->size == size && d_ptr->fbSize == framebufferSize) return;
-
- d_ptr->size = size;
- d_ptr->fbSize = framebufferSize;
+ auto size = sanitizedSize(size_);
- d_ptr->mapObj->setSize(sanitizedSize(size));
-}
+ if (d_ptr->mapObj->getSize() == size)
+ return;
-/*!
- If Mapbox GL needs to rebind the default \a fbo, it will use the
- ID supplied here.
-*/
-void QMapboxGL::setFramebufferObject(quint32 fbo) {
- d_ptr->fbObject = fbo;
+ d_ptr->mapObj->setSize(size);
}
/*!
@@ -1468,6 +1503,51 @@ void QMapboxGL::setFilter(const QString& layer, const QVariant& filter)
}
/*!
+ Creates the infrastructure needed for rendering the map. It
+ should be called before any call to render().
+
+ Must be called on the render thread.
+*/
+void QMapboxGL::createRenderer()
+{
+ d_ptr->createRenderer();
+}
+
+/*!
+ Destroys the infrastructure needed for rendering the map,
+ releasing resources.
+
+ Must be called on the render thread.
+*/
+void QMapboxGL::destroyRenderer()
+{
+ d_ptr->destroyRenderer();
+}
+
+/*!
+ Start a static rendering of the current state of the map. This
+ should only be called when the map is initialized in static mode.
+
+ \sa QMapboxGLSettings::MapMode
+*/
+void QMapboxGL::startStaticRender()
+{
+ d_ptr->mapObj->renderStill([this](std::exception_ptr err) {
+ QString what;
+
+ try {
+ if (err) {
+ std::rethrow_exception(err);
+ }
+ } catch(const std::exception& e) {
+ what = e.what();
+ }
+
+ emit staticRenderFinished(what);
+ });
+}
+
+/*!
Renders the map using OpenGL draw calls. It will make sure to bind the
framebuffer object before drawing; otherwise a valid OpenGL context is
expected with an appropriate OpenGL viewport state set for the size of
@@ -1475,28 +1555,33 @@ void QMapboxGL::setFilter(const QString& layer, const QVariant& filter)
This function should be called only after the signal needsRendering() is
emitted at least once.
+
+ Must be called on the render thread.
*/
void QMapboxGL::render()
{
-#if defined(__APPLE__) && QT_VERSION < 0x050000
- // FIXME Qt 4.x provides an incomplete FBO at start.
- // See https://bugreports.qt.io/browse/QTBUG-36802 for details.
- if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
- return;
- }
-#endif
-
- d_ptr->dirty = false;
d_ptr->render();
}
/*!
+ If Mapbox GL needs to rebind the default \a fbo, it will use the
+ ID supplied here. \a size is the size of the framebuffer, which
+ on high DPI screens is usually bigger than the map size.
+
+ Must be called on the render thread.
+*/
+void QMapboxGL::setFramebufferObject(quint32 fbo, const QSize& size)
+{
+ d_ptr->setFramebufferObject(fbo, size);
+}
+
+/*!
Informs the map that the network connection has been established, causing
all network requests that previously timed out to be retried immediately.
*/
void QMapboxGL::connectionEstablished()
{
- d_ptr->connectionEstablished();
+ mbgl::NetworkStatus::Reachable();
}
/*!
@@ -1510,6 +1595,16 @@ void QMapboxGL::connectionEstablished()
*/
/*!
+ \fn void QMapboxGL::staticRenderFinished(const QString &error)
+
+ This signal is emitted when a static map is fully drawn. Usually the next
+ step is to extract the map from a framebuffer into a container like a
+ QImage. \a error is set to a message when an error occurs.
+
+ \sa startStaticRender()
+*/
+
+/*!
\fn void QMapboxGL::mapChanged(QMapboxGL::MapChange change)
This signal is emitted when the state of the map has changed. This signal
@@ -1526,186 +1621,140 @@ void QMapboxGL::connectionEstablished()
\a copyrightsHtml is a string with a HTML snippet.
*/
-class QMapboxGLRendererFrontend;
-
-QMapboxGLPrivate::QMapboxGLPrivate(QMapboxGL *q, const QMapboxGLSettings &settings, const QSize &size_, qreal pixelRatio)
+QMapboxGLPrivate::QMapboxGLPrivate(QMapboxGL *q, const QMapboxGLSettings &settings, const QSize &size, qreal pixelRatio_)
: QObject(q)
- , size(size_)
- , q_ptr(q)
- , fileSourceObj(sharedDefaultFileSource(
+ , m_fileSourceObj(sharedDefaultFileSource(
settings.cacheDatabasePath().toStdString(),
settings.assetPath().toStdString(),
settings.cacheDatabaseMaximumSize()))
- , threadPool(mbgl::sharedThreadPool())
+ , m_threadPool(mbgl::sharedThreadPool())
+ , m_mode(settings.contextMode())
+ , m_pixelRatio(pixelRatio_)
{
- // Setup resource transform if needed
+ // Setup the FileSource
+ m_fileSourceObj->setAccessToken(settings.accessToken().toStdString());
+ m_fileSourceObj->setAPIBaseURL(settings.apiBaseUrl().toStdString());
+
if (settings.resourceTransform()) {
- m_resourceTransform =
- std::make_unique< mbgl::Actor<mbgl::ResourceTransform> >( *mbgl::Scheduler::GetCurrent(),
- [callback = settings.resourceTransform()]
- (mbgl::Resource::Kind , const std::string&& url_) -> std::string {
- return callback(std::move(url_));
- }
- );
- fileSourceObj->setResourceTransform(m_resourceTransform->self());
+ m_resourceTransform = std::make_unique<mbgl::Actor<mbgl::ResourceTransform>>(*mbgl::Scheduler::GetCurrent(),
+ [callback = settings.resourceTransform()] (mbgl::Resource::Kind, const std::string &&url_) -> std::string {
+ return callback(std::move(url_));
+ });
+ m_fileSourceObj->setResourceTransform(m_resourceTransform->self());
}
- // Setup and connect the renderer frontend
- frontend = std::make_unique<QMapboxGLRendererFrontend>(
- std::make_unique<mbgl::Renderer>(*this, pixelRatio, *fileSourceObj, *threadPool,
- static_cast<mbgl::GLContextMode>(settings.contextMode())),
- *this);
- connect(frontend.get(), SIGNAL(updated()), this, SLOT(invalidate()));
+ // Setup MapObserver
+ m_mapObserver = std::make_unique<QMapboxGLMapObserver>(this);
+
+ qRegisterMetaType<QMapboxGL::MapChange>("QMapboxGL::MapChange");
+
+ connect(m_mapObserver.get(), SIGNAL(mapChanged(QMapboxGL::MapChange)), q, SIGNAL(mapChanged(QMapboxGL::MapChange)));
+ connect(m_mapObserver.get(), SIGNAL(copyrightsChanged(QString)), q, SIGNAL(copyrightsChanged(QString)));
+ // Setup the Map object
mapObj = std::make_unique<mbgl::Map>(
- *frontend,
- *this, sanitizedSize(size),
- pixelRatio, *fileSourceObj, *threadPool,
- mbgl::MapMode::Continuous,
+ *this, // RendererFrontend
+ *m_mapObserver,
+ sanitizedSize(size),
+ m_pixelRatio, *m_fileSourceObj, *m_threadPool,
+ static_cast<mbgl::MapMode>(settings.mapMode()),
static_cast<mbgl::ConstrainMode>(settings.constrainMode()),
static_cast<mbgl::ViewportMode>(settings.viewportMode()));
- qRegisterMetaType<QMapboxGL::MapChange>("QMapboxGL::MapChange");
-
- fileSourceObj->setAccessToken(settings.accessToken().toStdString());
- fileSourceObj->setAPIBaseURL(settings.apiBaseUrl().toStdString());
-
- connect(this, SIGNAL(needsRendering()), q_ptr, SIGNAL(needsRendering()), Qt::QueuedConnection);
- connect(this, SIGNAL(mapChanged(QMapboxGL::MapChange)), q_ptr, SIGNAL(mapChanged(QMapboxGL::MapChange)), Qt::QueuedConnection);
- connect(this, SIGNAL(copyrightsChanged(QString)), q_ptr, SIGNAL(copyrightsChanged(QString)), Qt::QueuedConnection);
+ // Needs to be Queued to give time to discard redundant draw calls via the `renderQueued` flag.
+ connect(this, SIGNAL(needsRendering()), q, SIGNAL(needsRendering()), Qt::QueuedConnection);
}
QMapboxGLPrivate::~QMapboxGLPrivate()
{
}
-mbgl::Size QMapboxGLPrivate::getFramebufferSize() const {
- return sanitizedSize(fbSize);
-}
+void QMapboxGLPrivate::update(std::shared_ptr<mbgl::UpdateParameters> parameters)
+{
+ std::lock_guard<std::recursive_mutex> lock(m_mapRendererMutex);
-void QMapboxGLPrivate::updateAssumedState() {
- assumeFramebufferBinding(fbObject);
-#if QT_VERSION >= 0x050600
- assumeViewport(0, 0, getFramebufferSize());
-#endif
-}
+ if (!m_mapRenderer) {
+ return;
+ }
-void QMapboxGLPrivate::bind() {
- setFramebufferBinding(fbObject);
- setViewport(0, 0, getFramebufferSize());
-}
+ m_mapRenderer->updateParameters(std::move(parameters));
-void QMapboxGLPrivate::invalidate()
-{
- if (!dirty) {
- emit needsRendering();
- dirty = true;
- }
+ requestRendering();
}
-void QMapboxGLPrivate::render()
+void QMapboxGLPrivate::setObserver(mbgl::RendererObserver &observer)
{
- frontend->render();
-}
+ m_rendererObserver = std::make_shared<QMapboxGLRendererObserver>(
+ *mbgl::util::RunLoop::Get(), observer);
-void QMapboxGLPrivate::onCameraWillChange(mbgl::MapObserver::CameraChangeMode mode)
-{
- if (mode == mbgl::MapObserver::CameraChangeMode::Immediate) {
- emit mapChanged(QMapboxGL::MapChangeRegionWillChange);
- } else {
- emit mapChanged(QMapboxGL::MapChangeRegionWillChangeAnimated);
+ std::lock_guard<std::recursive_mutex> lock(m_mapRendererMutex);
+
+ if (m_mapRenderer) {
+ m_mapRenderer->setObserver(m_rendererObserver);
}
}
-void QMapboxGLPrivate::onCameraIsChanging()
+void QMapboxGLPrivate::createRenderer()
{
- emit mapChanged(QMapboxGL::MapChangeRegionIsChanging);
-}
+ std::lock_guard<std::recursive_mutex> lock(m_mapRendererMutex);
-void QMapboxGLPrivate::onCameraDidChange(mbgl::MapObserver::CameraChangeMode mode)
-{
- if (mode == mbgl::MapObserver::CameraChangeMode::Immediate) {
- emit mapChanged(QMapboxGL::MapChangeRegionDidChange);
- } else {
- emit mapChanged(QMapboxGL::MapChangeRegionDidChangeAnimated);
+ if (m_mapRenderer) {
+ return;
}
-}
-void QMapboxGLPrivate::onWillStartLoadingMap()
-{
- emit mapChanged(QMapboxGL::MapChangeWillStartLoadingMap);
-}
+ m_mapRenderer = std::make_unique<QMapboxGLMapRenderer>(
+ m_pixelRatio,
+ *m_fileSourceObj,
+ *m_threadPool,
+ m_mode
+ );
-void QMapboxGLPrivate::onDidFinishLoadingMap()
-{
- emit mapChanged(QMapboxGL::MapChangeDidFinishLoadingMap);
-}
+ connect(m_mapRenderer.get(), SIGNAL(needsRendering()), this, SLOT(requestRendering()));
-void QMapboxGLPrivate::onDidFailLoadingMap(std::exception_ptr)
-{
- emit mapChanged(QMapboxGL::MapChangeDidFailLoadingMap);
+ m_mapRenderer->setObserver(m_rendererObserver);
}
-void QMapboxGLPrivate::onWillStartRenderingFrame()
+void QMapboxGLPrivate::destroyRenderer()
{
- emit mapChanged(QMapboxGL::MapChangeWillStartRenderingFrame);
-}
+ std::lock_guard<std::recursive_mutex> lock(m_mapRendererMutex);
-void QMapboxGLPrivate::onDidFinishRenderingFrame(mbgl::MapObserver::RenderMode mode)
-{
- if (mode == mbgl::MapObserver::RenderMode::Partial) {
- emit mapChanged(QMapboxGL::MapChangeDidFinishRenderingFrame);
- } else {
- emit mapChanged(QMapboxGL::MapChangeDidFinishRenderingFrameFullyRendered);
- }
+ m_mapRenderer.reset();
}
-void QMapboxGLPrivate::onWillStartRenderingMap()
+void QMapboxGLPrivate::render()
{
- emit mapChanged(QMapboxGL::MapChangeWillStartLoadingMap);
-}
+ std::lock_guard<std::recursive_mutex> lock(m_mapRendererMutex);
-void QMapboxGLPrivate::onDidFinishRenderingMap(mbgl::MapObserver::RenderMode mode)
-{
- if (mode == mbgl::MapObserver::RenderMode::Partial) {
- emit mapChanged(QMapboxGL::MapChangeDidFinishRenderingMap);
- } else {
- emit mapChanged(QMapboxGL::MapChangeDidFinishRenderingMapFullyRendered);
+ if (!m_mapRenderer) {
+ createRenderer();
}
-}
-void QMapboxGLPrivate::onDidFinishLoadingStyle()
-{
- emit mapChanged(QMapboxGL::MapChangeDidFinishLoadingStyle);
+#if defined(__APPLE__) && QT_VERSION < 0x050000
+ // FIXME Qt 4.x provides an incomplete FBO at start.
+ // See https://bugreports.qt.io/browse/QTBUG-36802 for details.
+ if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
+ return;
+ }
+#endif
+
+ m_renderQueued.clear();
+ m_mapRenderer->render();
}
-void QMapboxGLPrivate::onSourceChanged(mbgl::style::Source&)
+void QMapboxGLPrivate::setFramebufferObject(quint32 fbo, const QSize& size)
{
- std::string attribution;
- for (const auto& source : mapObj->getStyle().getSources()) {
- // Avoid duplicates by using the most complete attribution HTML snippet.
- if (source->getAttribution() && (attribution.size() < source->getAttribution()->size()))
- attribution = *source->getAttribution();
+ std::lock_guard<std::recursive_mutex> lock(m_mapRendererMutex);
+
+ if (!m_mapRenderer) {
+ createRenderer();
}
- emit copyrightsChanged(QString::fromStdString(attribution));
- emit mapChanged(QMapboxGL::MapChangeSourceDidChange);
-}
-/*!
- Initializes an OpenGL extension function such as Vertex Array Objects (VAOs),
- required by Mapbox GL Native engine.
-*/
-mbgl::gl::ProcAddress QMapboxGLPrivate::getExtensionFunctionPointer(const char* name) {
-#if QT_VERSION >= 0x050000
- QOpenGLContext* thisContext = QOpenGLContext::currentContext();
- return thisContext->getProcAddress(name);
-#else
- const QGLContext* thisContext = QGLContext::currentContext();
- return reinterpret_cast<mbgl::gl::ProcAddress>(thisContext->getProcAddress(name));
-#endif
+ m_mapRenderer->updateFramebuffer(fbo, sanitizedSize(size));
}
-void QMapboxGLPrivate::connectionEstablished()
+void QMapboxGLPrivate::requestRendering()
{
- mbgl::NetworkStatus::Reachable();
+ if (!m_renderQueued.test_and_set()) {
+ emit needsRendering();
+ }
}
diff --git a/platform/qt/src/qmapboxgl_map_observer.cpp b/platform/qt/src/qmapboxgl_map_observer.cpp
new file mode 100644
index 0000000000..60c6b81841
--- /dev/null
+++ b/platform/qt/src/qmapboxgl_map_observer.cpp
@@ -0,0 +1,95 @@
+#include "qmapboxgl_map_observer.hpp"
+
+#include "qmapboxgl_p.hpp"
+
+QMapboxGLMapObserver::QMapboxGLMapObserver(QMapboxGLPrivate *d)
+ : d_ptr(d)
+{
+}
+
+QMapboxGLMapObserver::~QMapboxGLMapObserver()
+{
+}
+
+void QMapboxGLMapObserver::onCameraWillChange(mbgl::MapObserver::CameraChangeMode mode)
+{
+ if (mode == mbgl::MapObserver::CameraChangeMode::Immediate) {
+ emit mapChanged(QMapboxGL::MapChangeRegionWillChange);
+ } else {
+ emit mapChanged(QMapboxGL::MapChangeRegionWillChangeAnimated);
+ }
+}
+
+void QMapboxGLMapObserver::onCameraIsChanging()
+{
+ emit mapChanged(QMapboxGL::MapChangeRegionIsChanging);
+}
+
+void QMapboxGLMapObserver::onCameraDidChange(mbgl::MapObserver::CameraChangeMode mode)
+{
+ if (mode == mbgl::MapObserver::CameraChangeMode::Immediate) {
+ emit mapChanged(QMapboxGL::MapChangeRegionDidChange);
+ } else {
+ emit mapChanged(QMapboxGL::MapChangeRegionDidChangeAnimated);
+ }
+}
+
+void QMapboxGLMapObserver::onWillStartLoadingMap()
+{
+ emit mapChanged(QMapboxGL::MapChangeWillStartLoadingMap);
+}
+
+void QMapboxGLMapObserver::onDidFinishLoadingMap()
+{
+ emit mapChanged(QMapboxGL::MapChangeDidFinishLoadingMap);
+}
+
+void QMapboxGLMapObserver::onDidFailLoadingMap(std::exception_ptr)
+{
+ emit mapChanged(QMapboxGL::MapChangeDidFailLoadingMap);
+}
+
+void QMapboxGLMapObserver::onWillStartRenderingFrame()
+{
+ emit mapChanged(QMapboxGL::MapChangeWillStartRenderingFrame);
+}
+
+void QMapboxGLMapObserver::onDidFinishRenderingFrame(mbgl::MapObserver::RenderMode mode)
+{
+ if (mode == mbgl::MapObserver::RenderMode::Partial) {
+ emit mapChanged(QMapboxGL::MapChangeDidFinishRenderingFrame);
+ } else {
+ emit mapChanged(QMapboxGL::MapChangeDidFinishRenderingFrameFullyRendered);
+ }
+}
+
+void QMapboxGLMapObserver::onWillStartRenderingMap()
+{
+ emit mapChanged(QMapboxGL::MapChangeWillStartRenderingMap);
+}
+
+void QMapboxGLMapObserver::onDidFinishRenderingMap(mbgl::MapObserver::RenderMode mode)
+{
+ if (mode == mbgl::MapObserver::RenderMode::Partial) {
+ emit mapChanged(QMapboxGL::MapChangeDidFinishRenderingMap);
+ } else {
+ emit mapChanged(QMapboxGL::MapChangeDidFinishRenderingMapFullyRendered);
+ }
+}
+
+void QMapboxGLMapObserver::onDidFinishLoadingStyle()
+{
+ emit mapChanged(QMapboxGL::MapChangeDidFinishLoadingStyle);
+}
+
+void QMapboxGLMapObserver::onSourceChanged(mbgl::style::Source&)
+{
+ std::string attribution;
+ for (const auto& source : d_ptr->mapObj->getStyle().getSources()) {
+ // Avoid duplicates by using the most complete attribution HTML snippet.
+ if (source->getAttribution() && (attribution.size() < source->getAttribution()->size()))
+ attribution = *source->getAttribution();
+ }
+ emit copyrightsChanged(QString::fromStdString(attribution));
+ emit mapChanged(QMapboxGL::MapChangeSourceDidChange);
+}
diff --git a/platform/qt/src/qmapboxgl_map_observer.hpp b/platform/qt/src/qmapboxgl_map_observer.hpp
new file mode 100644
index 0000000000..c9d0581a90
--- /dev/null
+++ b/platform/qt/src/qmapboxgl_map_observer.hpp
@@ -0,0 +1,45 @@
+#pragma once
+
+#include "qmapboxgl.hpp"
+
+#include <mbgl/map/map_observer.hpp>
+#include <mbgl/style/style.hpp>
+
+#include <QObject>
+
+#include <exception>
+#include <memory>
+
+class QMapboxGLPrivate;
+
+class QMapboxGLMapObserver : public QObject, public mbgl::MapObserver
+{
+ Q_OBJECT
+
+public:
+ explicit QMapboxGLMapObserver(QMapboxGLPrivate *);
+ virtual ~QMapboxGLMapObserver();
+
+ // mbgl::MapObserver implementation.
+ void onCameraWillChange(mbgl::MapObserver::CameraChangeMode) final;
+ void onCameraIsChanging() final;
+ void onCameraDidChange(mbgl::MapObserver::CameraChangeMode) final;
+ void onWillStartLoadingMap() final;
+ void onDidFinishLoadingMap() final;
+ void onDidFailLoadingMap(std::exception_ptr) final;
+ void onWillStartRenderingFrame() final;
+ void onDidFinishRenderingFrame(mbgl::MapObserver::RenderMode) final;
+ void onWillStartRenderingMap() final;
+ void onDidFinishRenderingMap(mbgl::MapObserver::RenderMode) final;
+ void onDidFinishLoadingStyle() final;
+ void onSourceChanged(mbgl::style::Source&) final;
+
+signals:
+ void mapChanged(QMapboxGL::MapChange);
+ void copyrightsChanged(const QString &copyrightsHtml);
+
+private:
+ Q_DISABLE_COPY(QMapboxGLMapObserver)
+
+ QMapboxGLPrivate *d_ptr;
+};
diff --git a/platform/qt/src/qmapboxgl_map_renderer.cpp b/platform/qt/src/qmapboxgl_map_renderer.cpp
new file mode 100644
index 0000000000..7a9d1f6f78
--- /dev/null
+++ b/platform/qt/src/qmapboxgl_map_renderer.cpp
@@ -0,0 +1,86 @@
+#include "qmapboxgl_map_renderer.hpp"
+
+#include <QtGlobal>
+
+QMapboxGLMapRenderer::QMapboxGLMapRenderer(qreal pixelRatio,
+ mbgl::DefaultFileSource &fs, mbgl::ThreadPool &tp, QMapboxGLSettings::GLContextMode mode)
+ : m_renderer(std::make_unique<mbgl::Renderer>(m_backend, pixelRatio, fs, tp, static_cast<mbgl::GLContextMode>(mode)))
+ , m_threadWithScheduler(Scheduler::GetCurrent() != nullptr)
+{
+}
+
+QMapboxGLMapRenderer::~QMapboxGLMapRenderer()
+{
+ MBGL_VERIFY_THREAD(tid);
+}
+
+void QMapboxGLMapRenderer::schedule(std::weak_ptr<mbgl::Mailbox> mailbox)
+{
+ std::lock_guard<std::mutex> lock(m_taskQueueMutex);
+ m_taskQueue.push(mailbox);
+
+ // Need to force the main thread to wake
+ // up this thread and process the events.
+ emit needsRendering();
+}
+
+void QMapboxGLMapRenderer::updateParameters(std::shared_ptr<mbgl::UpdateParameters> newParameters)
+{
+ std::lock_guard<std::mutex> lock(m_updateMutex);
+ m_updateParameters = std::move(newParameters);
+}
+
+void QMapboxGLMapRenderer::updateFramebuffer(quint32 fbo, const mbgl::Size &size)
+{
+ MBGL_VERIFY_THREAD(tid);
+
+ m_backend.updateFramebuffer(fbo, size);
+}
+
+void QMapboxGLMapRenderer::render()
+{
+ MBGL_VERIFY_THREAD(tid);
+
+ std::shared_ptr<mbgl::UpdateParameters> params;
+ {
+ // Lock on the parameters
+ std::lock_guard<std::mutex> lock(m_updateMutex);
+
+ if (!m_updateParameters) {
+ return;
+ }
+
+ // Hold on to the update parameters during render
+ params = m_updateParameters;
+ }
+
+ // The OpenGL implementation automatically enables the OpenGL context for us.
+ mbgl::BackendScope scope(m_backend, mbgl::BackendScope::ScopeType::Implicit);
+
+ // If we don't have a Scheduler on this thread, which
+ // is usually the case for render threads, use this
+ // object as scheduler.
+ if (!m_threadWithScheduler) {
+ Scheduler::SetCurrent(this);
+ }
+
+ m_renderer->render(*params);
+
+ if (!m_threadWithScheduler) {
+ std::queue<std::weak_ptr<mbgl::Mailbox>> taskQueue;
+ {
+ std::unique_lock<std::mutex> lock(m_taskQueueMutex);
+ std::swap(taskQueue, m_taskQueue);
+ }
+
+ while (!taskQueue.empty()) {
+ mbgl::Mailbox::maybeReceive(taskQueue.front());
+ taskQueue.pop();
+ }
+ }
+}
+
+void QMapboxGLMapRenderer::setObserver(std::shared_ptr<mbgl::RendererObserver> observer)
+{
+ m_renderer->setObserver(observer.get());
+}
diff --git a/platform/qt/src/qmapboxgl_map_renderer.hpp b/platform/qt/src/qmapboxgl_map_renderer.hpp
new file mode 100644
index 0000000000..adba11de51
--- /dev/null
+++ b/platform/qt/src/qmapboxgl_map_renderer.hpp
@@ -0,0 +1,63 @@
+#pragma once
+
+#include "qmapboxgl.hpp"
+#include "qmapboxgl_renderer_backend.hpp"
+
+#include <mbgl/renderer/renderer.hpp>
+#include <mbgl/renderer/renderer_backend.hpp>
+#include <mbgl/renderer/renderer_observer.hpp>
+#include <mbgl/storage/default_file_source.hpp>
+#include <mbgl/util/shared_thread_pool.hpp>
+#include <mbgl/util/util.hpp>
+
+#include <QtGlobal>
+
+#include <memory>
+#include <mutex>
+#include <queue>
+
+namespace mbgl {
+class Renderer;
+class UpdateParameters;
+} // namespace mbgl
+
+class QMapboxGLRendererBackend;
+
+class QMapboxGLMapRenderer : public QObject, public mbgl::Scheduler
+{
+ Q_OBJECT
+
+public:
+ QMapboxGLMapRenderer(qreal pixelRatio, mbgl::DefaultFileSource &,
+ mbgl::ThreadPool &, QMapboxGLSettings::GLContextMode);
+ virtual ~QMapboxGLMapRenderer();
+
+ // mbgl::Scheduler implementation.
+ void schedule(std::weak_ptr<mbgl::Mailbox> scheduled) final;
+
+ void render();
+ void updateFramebuffer(quint32 fbo, const mbgl::Size &size);
+ void setObserver(std::shared_ptr<mbgl::RendererObserver>);
+
+ // Thread-safe, called by the Frontend
+ void updateParameters(std::shared_ptr<mbgl::UpdateParameters>);
+
+signals:
+ void needsRendering();
+
+private:
+ MBGL_STORE_THREAD(tid)
+
+ Q_DISABLE_COPY(QMapboxGLMapRenderer)
+
+ std::mutex m_updateMutex;
+ std::shared_ptr<mbgl::UpdateParameters> m_updateParameters;
+
+ QMapboxGLRendererBackend m_backend;
+ std::unique_ptr<mbgl::Renderer> m_renderer;
+
+ std::mutex m_taskQueueMutex;
+ std::queue<std::weak_ptr<mbgl::Mailbox>> m_taskQueue;
+
+ bool m_threadWithScheduler;
+};
diff --git a/platform/qt/src/qmapboxgl_p.hpp b/platform/qt/src/qmapboxgl_p.hpp
index f947c09f48..51c7cb8fc4 100644
--- a/platform/qt/src/qmapboxgl_p.hpp
+++ b/platform/qt/src/qmapboxgl_p.hpp
@@ -1,22 +1,24 @@
#pragma once
#include "qmapboxgl.hpp"
-#include "qmapboxgl_renderer_frontend_p.hpp"
+#include "qmapboxgl_map_observer.hpp"
+#include "qmapboxgl_map_renderer.hpp"
#include <mbgl/actor/actor.hpp>
#include <mbgl/map/map.hpp>
-#include <mbgl/renderer/renderer_backend.hpp>
-#include <mbgl/util/default_thread_pool.hpp>
+#include <mbgl/renderer/renderer_frontend.hpp>
#include <mbgl/storage/default_file_source.hpp>
-#include <mbgl/util/geo.hpp>
#include <mbgl/storage/resource_transform.hpp>
+#include <mbgl/util/default_thread_pool.hpp>
+#include <mbgl/util/geo.hpp>
#include <QObject>
#include <QSize>
+#include <atomic>
#include <memory>
-class QMapboxGLPrivate : public QObject, public mbgl::RendererBackend, public mbgl::MapObserver
+class QMapboxGLPrivate : public QObject, public mbgl::RendererFrontend
{
Q_OBJECT
@@ -24,55 +26,40 @@ public:
explicit QMapboxGLPrivate(QMapboxGL *, const QMapboxGLSettings &, const QSize &size, qreal pixelRatio);
virtual ~QMapboxGLPrivate();
+ // mbgl::RendererFrontend implementation.
+ void reset() final {}
+ void setObserver(mbgl::RendererObserver &) final;
+ void update(std::shared_ptr<mbgl::UpdateParameters>) final;
- // mbgl::RendererBackend implementation.
- void bind() final;
- mbgl::Size getFramebufferSize() const final;
- void updateAssumedState() final;
- void activate() final {}
- void deactivate() final {}
-
- // mbgl::MapObserver implementation.
- void onCameraWillChange(mbgl::MapObserver::CameraChangeMode) final;
- void onCameraIsChanging() final;
- void onCameraDidChange(mbgl::MapObserver::CameraChangeMode) final;
- void onWillStartLoadingMap() final;
- void onDidFinishLoadingMap() final;
- void onDidFailLoadingMap(std::exception_ptr) final;
- void onWillStartRenderingFrame() final;
- void onDidFinishRenderingFrame(mbgl::MapObserver::RenderMode) final;
- void onWillStartRenderingMap() final;
- void onDidFinishRenderingMap(mbgl::MapObserver::RenderMode) final;
- void onDidFinishLoadingStyle() final;
- void onSourceChanged(mbgl::style::Source&) final;
+ // These need to be called on the same thread.
+ void createRenderer();
+ void destroyRenderer();
+ void render();
+ void setFramebufferObject(quint32 fbo, const QSize& size);
mbgl::EdgeInsets margins;
- QSize size { 0, 0 };
- QSize fbSize { 0, 0 };
- quint32 fbObject = 0;
-
- QMapboxGL *q_ptr { nullptr };
-
- std::shared_ptr<mbgl::DefaultFileSource> fileSourceObj;
- std::shared_ptr<mbgl::ThreadPool> threadPool;
- std::unique_ptr<QMapboxGLRendererFrontend> frontend;
std::unique_ptr<mbgl::Map> mapObj;
- bool dirty { false };
-
-private:
- mbgl::gl::ProcAddress getExtensionFunctionPointer(const char*) override;
-
public slots:
- void connectionEstablished();
- void invalidate();
- void render();
+ void requestRendering();
signals:
void needsRendering();
- void mapChanged(QMapboxGL::MapChange);
- void copyrightsChanged(const QString &copyrightsHtml);
private:
- std::unique_ptr< mbgl::Actor<mbgl::ResourceTransform> > m_resourceTransform;
+ Q_DISABLE_COPY(QMapboxGLPrivate)
+
+ std::recursive_mutex m_mapRendererMutex;
+ std::shared_ptr<mbgl::RendererObserver> m_rendererObserver;
+
+ std::unique_ptr<QMapboxGLMapObserver> m_mapObserver;
+ std::shared_ptr<mbgl::DefaultFileSource> m_fileSourceObj;
+ std::shared_ptr<mbgl::ThreadPool> m_threadPool;
+ std::unique_ptr<QMapboxGLMapRenderer> m_mapRenderer;
+ std::unique_ptr<mbgl::Actor<mbgl::ResourceTransform>> m_resourceTransform;
+
+ QMapboxGLSettings::GLContextMode m_mode;
+ qreal m_pixelRatio;
+
+ std::atomic_flag m_renderQueued = ATOMIC_FLAG_INIT;
};
diff --git a/platform/qt/src/qmapboxgl_renderer_backend.cpp b/platform/qt/src/qmapboxgl_renderer_backend.cpp
new file mode 100644
index 0000000000..917741f5ce
--- /dev/null
+++ b/platform/qt/src/qmapboxgl_renderer_backend.cpp
@@ -0,0 +1,49 @@
+#include "qmapboxgl_renderer_backend.hpp"
+
+#include <QtGlobal>
+
+#if QT_VERSION >= 0x050000
+#include <QOpenGLContext>
+#else
+#include <QGLContext>
+#endif
+
+void QMapboxGLRendererBackend::updateAssumedState()
+{
+ assumeFramebufferBinding(ImplicitFramebufferBinding);
+ assumeViewport(0, 0, m_size);
+}
+
+void QMapboxGLRendererBackend::bind()
+{
+ assert(mbgl::BackendScope::exists());
+
+ setFramebufferBinding(m_fbo);
+ setViewport(0, 0, m_size);
+}
+
+mbgl::Size QMapboxGLRendererBackend::getFramebufferSize() const
+{
+ return m_size;
+}
+
+void QMapboxGLRendererBackend::updateFramebuffer(quint32 fbo, const mbgl::Size &size)
+{
+ m_fbo = fbo;
+ m_size = size;
+}
+
+/*!
+ Initializes an OpenGL extension function such as Vertex Array Objects (VAOs),
+ required by Mapbox GL Native engine.
+*/
+mbgl::gl::ProcAddress QMapboxGLRendererBackend::getExtensionFunctionPointer(const char* name)
+{
+#if QT_VERSION >= 0x050000
+ QOpenGLContext* thisContext = QOpenGLContext::currentContext();
+ return thisContext->getProcAddress(name);
+#else
+ const QGLContext* thisContext = QGLContext::currentContext();
+ return reinterpret_cast<mbgl::gl::ProcAddress>(thisContext->getProcAddress(name));
+#endif
+}
diff --git a/platform/qt/src/qmapboxgl_renderer_backend.hpp b/platform/qt/src/qmapboxgl_renderer_backend.hpp
new file mode 100644
index 0000000000..de66b035fc
--- /dev/null
+++ b/platform/qt/src/qmapboxgl_renderer_backend.hpp
@@ -0,0 +1,34 @@
+#pragma once
+
+#include "qmapboxgl.hpp"
+
+#include <mbgl/renderer/renderer_backend.hpp>
+#include <mbgl/storage/default_file_source.hpp>
+#include <mbgl/util/shared_thread_pool.hpp>
+
+class QMapboxGLRendererBackend : public mbgl::RendererBackend
+{
+public:
+ QMapboxGLRendererBackend() = default;
+ virtual ~QMapboxGLRendererBackend() = default;
+
+ // mbgl::RendererBackend implementation
+ void updateAssumedState() final;
+ void bind() final;
+ mbgl::Size getFramebufferSize() const final;
+
+ void updateFramebuffer(quint32 fbo, const mbgl::Size &);
+
+protected:
+ mbgl::gl::ProcAddress getExtensionFunctionPointer(const char*) final;
+
+ // No-op, implicit mode.
+ void activate() final {}
+ void deactivate() final {}
+
+private:
+ quint32 m_fbo = 0;
+ mbgl::Size m_size = { 0, 0 };
+
+ Q_DISABLE_COPY(QMapboxGLRendererBackend)
+};
diff --git a/platform/qt/src/qmapboxgl_renderer_frontend_p.cpp b/platform/qt/src/qmapboxgl_renderer_frontend_p.cpp
deleted file mode 100644
index ea60851eb4..0000000000
--- a/platform/qt/src/qmapboxgl_renderer_frontend_p.cpp
+++ /dev/null
@@ -1,37 +0,0 @@
-#include "qmapboxgl_renderer_frontend_p.hpp"
-
-#include <mbgl/renderer/backend_scope.hpp>
-#include <mbgl/renderer/renderer.hpp>
-
-QMapboxGLRendererFrontend::QMapboxGLRendererFrontend(std::unique_ptr<mbgl::Renderer> renderer_, mbgl::RendererBackend& backend_)
- : renderer(std::move(renderer_))
- , backend(backend_) {
-}
-
-QMapboxGLRendererFrontend::~QMapboxGLRendererFrontend() = default;
-
-void QMapboxGLRendererFrontend::reset() {
- if (renderer) {
- renderer.reset();
- }
-}
-
-void QMapboxGLRendererFrontend::update(std::shared_ptr<mbgl::UpdateParameters> updateParameters_) {
- updateParameters = updateParameters_;
- emit updated();
-}
-
-void QMapboxGLRendererFrontend::setObserver(mbgl::RendererObserver& observer_) {
- if (!renderer) return;
-
- renderer->setObserver(&observer_);
-}
-
-void QMapboxGLRendererFrontend::render() {
- if (!renderer || !updateParameters) return;
-
- // The OpenGL implementation automatically enables the OpenGL context for us.
- mbgl::BackendScope scope { backend, mbgl::BackendScope::ScopeType::Implicit };
-
- renderer->render(*updateParameters);
-}
diff --git a/platform/qt/src/qmapboxgl_renderer_frontend_p.hpp b/platform/qt/src/qmapboxgl_renderer_frontend_p.hpp
deleted file mode 100644
index c5e2bacc34..0000000000
--- a/platform/qt/src/qmapboxgl_renderer_frontend_p.hpp
+++ /dev/null
@@ -1,35 +0,0 @@
-#pragma once
-
-#include <mbgl/renderer/renderer_backend.hpp>
-#include <mbgl/renderer/renderer_frontend.hpp>
-
-#include <QObject>
-
-namespace mbgl {
- class Renderer;
-} // namespace mbgl
-
-class QMapboxGLRendererFrontend : public QObject, public mbgl::RendererFrontend
-{
- Q_OBJECT
-
-public:
- explicit QMapboxGLRendererFrontend(std::unique_ptr<mbgl::Renderer>, mbgl::RendererBackend&);
- ~QMapboxGLRendererFrontend() override;
-
- void reset() override;
- void setObserver(mbgl::RendererObserver&) override;
-
- void update(std::shared_ptr<mbgl::UpdateParameters>) override;
-
-public slots:
- void render();
-
-signals:
- void updated();
-
-private:
- std::unique_ptr<mbgl::Renderer> renderer;
- mbgl::RendererBackend& backend;
- std::shared_ptr<mbgl::UpdateParameters> updateParameters;
-};
diff --git a/platform/qt/src/qmapboxgl_renderer_observer.hpp b/platform/qt/src/qmapboxgl_renderer_observer.hpp
new file mode 100644
index 0000000000..ee340113ff
--- /dev/null
+++ b/platform/qt/src/qmapboxgl_renderer_observer.hpp
@@ -0,0 +1,51 @@
+#pragma once
+
+#include <mbgl/actor/actor.hpp>
+#include <mbgl/actor/actor_ref.hpp>
+#include <mbgl/actor/mailbox.hpp>
+#include <mbgl/renderer/renderer_observer.hpp>
+#include <mbgl/util/run_loop.hpp>
+
+#include <memory>
+
+// Forwards RendererObserver signals to the given
+// Delegate RendererObserver on the given RunLoop
+class QMapboxGLRendererObserver : public mbgl::RendererObserver {
+public:
+ QMapboxGLRendererObserver(mbgl::util::RunLoop& mapRunLoop, mbgl::RendererObserver& delegate_)
+ : mailbox(std::make_shared<mbgl::Mailbox>(mapRunLoop))
+ , delegate(delegate_, mailbox) {
+ }
+
+ ~QMapboxGLRendererObserver() {
+ mailbox->close();
+ }
+
+ void onInvalidate() final {
+ delegate.invoke(&mbgl::RendererObserver::onInvalidate);
+ }
+
+ void onResourceError(std::exception_ptr err) final {
+ delegate.invoke(&mbgl::RendererObserver::onResourceError, err);
+ }
+
+ void onWillStartRenderingMap() final {
+ delegate.invoke(&mbgl::RendererObserver::onWillStartRenderingMap);
+ }
+
+ void onWillStartRenderingFrame() final {
+ delegate.invoke(&mbgl::RendererObserver::onWillStartRenderingFrame);
+ }
+
+ void onDidFinishRenderingFrame(RenderMode mode, bool repaintNeeded) final {
+ delegate.invoke(&mbgl::RendererObserver::onDidFinishRenderingFrame, mode, repaintNeeded);
+ }
+
+ void onDidFinishRenderingMap() final {
+ delegate.invoke(&mbgl::RendererObserver::onDidFinishRenderingMap);
+ }
+
+private:
+ std::shared_ptr<mbgl::Mailbox> mailbox;
+ mbgl::ActorRef<mbgl::RendererObserver> delegate;
+};
diff --git a/platform/qt/src/qt_logging.cpp b/platform/qt/src/qt_logging.cpp
index acbe9562d0..acbe9562d0 100755..100644
--- a/platform/qt/src/qt_logging.cpp
+++ b/platform/qt/src/qt_logging.cpp
diff --git a/platform/qt/src/run_loop.cpp b/platform/qt/src/run_loop.cpp
index af0c50ebb9..c25243c8e7 100644
--- a/platform/qt/src/run_loop.cpp
+++ b/platform/qt/src/run_loop.cpp
@@ -52,11 +52,8 @@ LOOP_HANDLE RunLoop::getLoopHandle() {
return nullptr;
}
-void RunLoop::push(std::shared_ptr<WorkTask> task) {
- withMutex([&] {
- queue.push(std::move(task));
- impl->async->send();
- });
+void RunLoop::wake() {
+ impl->async->send();
}
void RunLoop::run() {
diff --git a/platform/qt/src/sqlite3.cpp b/platform/qt/src/sqlite3.cpp
index 80efd6a326..4bcaea0e31 100644
--- a/platform/qt/src/sqlite3.cpp
+++ b/platform/qt/src/sqlite3.cpp
@@ -24,11 +24,11 @@ namespace mapbox {
namespace sqlite {
// https://www.sqlite.org/rescode.html#ok
-static_assert(mbgl::underlying_type(Exception::OK) == 0, "error");
+static_assert(mbgl::underlying_type(ResultCode::OK) == 0, "error");
// https://www.sqlite.org/rescode.html#cantopen
-static_assert(mbgl::underlying_type(Exception::CANTOPEN) == 14, "error");
+static_assert(mbgl::underlying_type(ResultCode::CantOpen) == 14, "error");
// https://www.sqlite.org/rescode.html#notadb
-static_assert(mbgl::underlying_type(Exception::NOTADB) == 26, "error");
+static_assert(mbgl::underlying_type(ResultCode::NotADB) == 26, "error");
void checkQueryError(const QSqlQuery& query) {
QSqlError lastError = query.lastError();
@@ -57,7 +57,7 @@ void checkDatabaseOpenError(const QSqlDatabase &db) {
// always returns -1 for `nativeErrorCode()` on database errors.
QSqlError lastError = db.lastError();
if (lastError.type() != QSqlError::NoError) {
- throw Exception { Exception::Code::CANTOPEN, "Error opening the database." };
+ throw Exception { ResultCode::CantOpen, "Error opening the database." };
}
}
@@ -74,7 +74,7 @@ public:
: connectionName(QString::number(uint64_t(QThread::currentThread())) + incrementCounter())
{
if (!QSqlDatabase::drivers().contains("QSQLITE")) {
- throw Exception { Exception::Code::CANTOPEN, "SQLite driver not found." };
+ throw Exception { ResultCode::CantOpen, "SQLite driver not found." };
}
assert(!QSqlDatabase::contains(connectionName));
@@ -186,74 +186,82 @@ void Database::exec(const std::string &sql) {
}
}
-Statement Database::prepare(const char *query) {
- return Statement(this, query);
-}
-
-Statement::Statement(Database *db, const char *sql)
- : impl(std::make_unique<StatementImpl>(QString(sql), QSqlDatabase::database(db->impl->connectionName))) {
+Statement::Statement(Database& db, const char* sql)
+ : impl(std::make_unique<StatementImpl>(QString(sql),
+ QSqlDatabase::database(db.impl->connectionName))) {
assert(impl);
}
-Statement::Statement(Statement &&other)
- : impl(std::move(other.impl)) {
- assert(impl);
+Statement::~Statement() {
+#ifndef NDEBUG
+ // Crash if we're destructing this object while we know a Query object references this.
+ assert(!used);
+#endif
}
-Statement &Statement::operator=(Statement &&other) {
- assert(impl);
- std::swap(impl, other.impl);
- return *this;
+Query::Query(Statement& stmt_) : stmt(stmt_) {
+ assert(stmt.impl);
+
+#ifndef NDEBUG
+ assert(!stmt.used);
+ stmt.used = true;
+#endif
}
-Statement::~Statement() {
+Query::~Query() {
+ reset();
+ clearBindings();
+
+#ifndef NDEBUG
+ stmt.used = false;
+#endif
}
-template void Statement::bind(int, int64_t);
+template void Query::bind(int, int64_t);
template <typename T>
-void Statement::bind(int offset, T value) {
- assert(impl);
+void Query::bind(int offset, T value) {
+ assert(stmt.impl);
// Field numbering starts at 0.
- impl->query.bindValue(offset - 1, QVariant::fromValue<T>(value), QSql::In);
- checkQueryError(impl->query);
+ stmt.impl->query.bindValue(offset - 1, QVariant::fromValue<T>(value), QSql::In);
+ checkQueryError(stmt.impl->query);
}
template <>
-void Statement::bind(int offset, std::nullptr_t) {
- assert(impl);
+void Query::bind(int offset, std::nullptr_t) {
+ assert(stmt.impl);
// Field numbering starts at 0.
- impl->query.bindValue(offset - 1, QVariant(QVariant::Invalid), QSql::In);
- checkQueryError(impl->query);
+ stmt.impl->query.bindValue(offset - 1, QVariant(QVariant::Invalid), QSql::In);
+ checkQueryError(stmt.impl->query);
}
template <>
-void Statement::bind(int offset, int32_t value) {
+void Query::bind(int offset, int32_t value) {
bind(offset, static_cast<int64_t>(value));
}
template <>
-void Statement::bind(int offset, bool value) {
+void Query::bind(int offset, bool value) {
bind(offset, static_cast<int>(value));
}
template <>
-void Statement::bind(int offset, int8_t value) {
+void Query::bind(int offset, int8_t value) {
bind(offset, static_cast<int64_t>(value));
}
template <>
-void Statement::bind(int offset, uint8_t value) {
+void Query::bind(int offset, uint8_t value) {
bind(offset, static_cast<int64_t>(value));
}
template <>
-void Statement::bind(int offset, mbgl::Timestamp value) {
+void Query::bind(int offset, mbgl::Timestamp value) {
bind(offset, std::chrono::system_clock::to_time_t(value));
}
template <>
-void Statement::bind(int offset, optional<std::string> value) {
+void Query::bind(int offset, optional<std::string> value) {
if (value) {
bind(offset, *value);
} else {
@@ -262,7 +270,7 @@ void Statement::bind(int offset, optional<std::string> value) {
}
template <>
-void Statement::bind(int offset, optional<mbgl::Timestamp> value) {
+void Query::bind(int offset, optional<mbgl::Timestamp> value) {
if (value) {
bind(offset, *value);
} else {
@@ -270,30 +278,25 @@ void Statement::bind(int offset, optional<mbgl::Timestamp> value) {
}
}
-void Statement::bind(int offset, const char* value, std::size_t length, bool retain) {
- assert(impl);
+void Query::bind(int offset, const char* value, std::size_t length, bool /* retain */) {
+ assert(stmt.impl);
if (length > std::numeric_limits<int>::max()) {
// Kept for consistence with the default implementation.
throw std::range_error("value too long");
}
- // Qt SQLite driver treats QByteArray as blob: we need to explicitly
- // declare the variant type as string.
- QVariant text(QVariant::Type::String);
- text.setValue(retain ? QByteArray(value, length) : QByteArray::fromRawData(value, length));
-
// Field numbering starts at 0.
- impl->query.bindValue(offset - 1, std::move(text), QSql::In);
+ stmt.impl->query.bindValue(offset - 1, QString(QByteArray(value, length)), QSql::In);
- checkQueryError(impl->query);
+ checkQueryError(stmt.impl->query);
}
-void Statement::bind(int offset, const std::string& value, bool retain) {
+void Query::bind(int offset, const std::string& value, bool retain) {
bind(offset, value.data(), value.size(), retain);
}
-void Statement::bindBlob(int offset, const void* value_, std::size_t length, bool retain) {
- assert(impl);
+void Query::bindBlob(int offset, const void* value_, std::size_t length, bool retain) {
+ assert(stmt.impl);
const char* value = reinterpret_cast<const char*>(value_);
if (length > std::numeric_limits<int>::max()) {
// Kept for consistence with the default implementation.
@@ -301,123 +304,123 @@ void Statement::bindBlob(int offset, const void* value_, std::size_t length, boo
}
// Field numbering starts at 0.
- impl->query.bindValue(offset - 1, retain ? QByteArray(value, length) :
+ stmt.impl->query.bindValue(offset - 1, retain ? QByteArray(value, length) :
QByteArray::fromRawData(value, length), QSql::In | QSql::Binary);
- checkQueryError(impl->query);
+ checkQueryError(stmt.impl->query);
}
-void Statement::bindBlob(int offset, const std::vector<uint8_t>& value, bool retain) {
+void Query::bindBlob(int offset, const std::vector<uint8_t>& value, bool retain) {
bindBlob(offset, value.data(), value.size(), retain);
}
-bool Statement::run() {
- assert(impl);
+bool Query::run() {
+ assert(stmt.impl);
- if (!impl->query.isValid()) {
- if (impl->query.exec()) {
- impl->lastInsertRowId = impl->query.lastInsertId().value<int64_t>();
- impl->changes = impl->query.numRowsAffected();
+ if (!stmt.impl->query.isValid()) {
+ if (stmt.impl->query.exec()) {
+ stmt.impl->lastInsertRowId = stmt.impl->query.lastInsertId().value<int64_t>();
+ stmt.impl->changes = stmt.impl->query.numRowsAffected();
} else {
- checkQueryError(impl->query);
+ checkQueryError(stmt.impl->query);
}
}
- const bool hasNext = impl->query.next();
- if (!hasNext) impl->query.finish();
+ const bool hasNext = stmt.impl->query.next();
+ if (!hasNext) stmt.impl->query.finish();
return hasNext;
}
-template bool Statement::get(int);
-template int Statement::get(int);
-template int64_t Statement::get(int);
-template double Statement::get(int);
+template bool Query::get(int);
+template int Query::get(int);
+template int64_t Query::get(int);
+template double Query::get(int);
-template <typename T> T Statement::get(int offset) {
- assert(impl && impl->query.isValid());
- QVariant value = impl->query.value(offset);
- checkQueryError(impl->query);
+template <typename T> T Query::get(int offset) {
+ assert(stmt.impl && stmt.impl->query.isValid());
+ QVariant value = stmt.impl->query.value(offset);
+ checkQueryError(stmt.impl->query);
return value.value<T>();
}
-template <> std::vector<uint8_t> Statement::get(int offset) {
- assert(impl && impl->query.isValid());
- QByteArray byteArray = impl->query.value(offset).toByteArray();
- checkQueryError(impl->query);
+template <> std::vector<uint8_t> Query::get(int offset) {
+ assert(stmt.impl && stmt.impl->query.isValid());
+ QByteArray byteArray = stmt.impl->query.value(offset).toByteArray();
+ checkQueryError(stmt.impl->query);
std::vector<uint8_t> blob(byteArray.begin(), byteArray.end());
return blob;
}
-template <> mbgl::Timestamp Statement::get(int offset) {
- assert(impl && impl->query.isValid());
- QVariant value = impl->query.value(offset);
- checkQueryError(impl->query);
+template <> mbgl::Timestamp Query::get(int offset) {
+ assert(stmt.impl && stmt.impl->query.isValid());
+ QVariant value = stmt.impl->query.value(offset);
+ checkQueryError(stmt.impl->query);
return std::chrono::time_point_cast<std::chrono::seconds>(
std::chrono::system_clock::from_time_t(value.value<::time_t>()));
}
-template <> optional<int64_t> Statement::get(int offset) {
- assert(impl && impl->query.isValid());
- QVariant value = impl->query.value(offset);
- checkQueryError(impl->query);
+template <> optional<int64_t> Query::get(int offset) {
+ assert(stmt.impl && stmt.impl->query.isValid());
+ QVariant value = stmt.impl->query.value(offset);
+ checkQueryError(stmt.impl->query);
if (value.isNull())
return {};
return { value.value<int64_t>() };
}
-template <> optional<double> Statement::get(int offset) {
- assert(impl && impl->query.isValid());
- QVariant value = impl->query.value(offset);
- checkQueryError(impl->query);
+template <> optional<double> Query::get(int offset) {
+ assert(stmt.impl && stmt.impl->query.isValid());
+ QVariant value = stmt.impl->query.value(offset);
+ checkQueryError(stmt.impl->query);
if (value.isNull())
return {};
return { value.value<double>() };
}
-template <> std::string Statement::get(int offset) {
- assert(impl && impl->query.isValid());
- QByteArray value = impl->query.value(offset).toByteArray();
- checkQueryError(impl->query);
+template <> std::string Query::get(int offset) {
+ assert(stmt.impl && stmt.impl->query.isValid());
+ QByteArray value = stmt.impl->query.value(offset).toByteArray();
+ checkQueryError(stmt.impl->query);
return std::string(value.constData(), value.size());
}
-template <> optional<std::string> Statement::get(int offset) {
- assert(impl && impl->query.isValid());
- QByteArray value = impl->query.value(offset).toByteArray();
- checkQueryError(impl->query);
+template <> optional<std::string> Query::get(int offset) {
+ assert(stmt.impl && stmt.impl->query.isValid());
+ QByteArray value = stmt.impl->query.value(offset).toByteArray();
+ checkQueryError(stmt.impl->query);
if (value.isNull())
return {};
return { std::string(value.constData(), value.size()) };
}
-template <> optional<mbgl::Timestamp> Statement::get(int offset) {
- assert(impl && impl->query.isValid());
- QVariant value = impl->query.value(offset);
- checkQueryError(impl->query);
+template <> optional<mbgl::Timestamp> Query::get(int offset) {
+ assert(stmt.impl && stmt.impl->query.isValid());
+ QVariant value = stmt.impl->query.value(offset);
+ checkQueryError(stmt.impl->query);
if (value.isNull())
return {};
return { std::chrono::time_point_cast<mbgl::Seconds>(
std::chrono::system_clock::from_time_t(value.value<::time_t>())) };
}
-void Statement::reset() {
- assert(impl);
- impl->query.finish();
+void Query::reset() {
+ assert(stmt.impl);
+ stmt.impl->query.finish();
}
-void Statement::clearBindings() {
+void Query::clearBindings() {
// no-op
}
-int64_t Statement::lastInsertRowId() const {
- assert(impl);
- return impl->lastInsertRowId;
+int64_t Query::lastInsertRowId() const {
+ assert(stmt.impl);
+ return stmt.impl->lastInsertRowId;
}
-uint64_t Statement::changes() const {
- assert(impl);
- return (impl->changes < 0 ? 0 : impl->changes);
+uint64_t Query::changes() const {
+ assert(stmt.impl);
+ return (stmt.impl->changes < 0 ? 0 : stmt.impl->changes);
}
Transaction::Transaction(Database& db_, Mode mode)
diff --git a/platform/qt/test/qmapboxgl.test.cpp b/platform/qt/test/qmapboxgl.test.cpp
index 932460b932..2a56b346a3 100644
--- a/platform/qt/test/qmapboxgl.test.cpp
+++ b/platform/qt/test/qmapboxgl.test.cpp
@@ -15,8 +15,8 @@ QMapboxGLTest::QMapboxGLTest() : size(512, 512), fbo((assert(widget.context()->i
this, SLOT(onMapChanged(QMapboxGL::MapChange)));
connect(&map, SIGNAL(needsRendering()),
this, SLOT(onNeedsRendering()));
- map.resize(fbo.size(), fbo.size());
- map.setFramebufferObject(fbo.handle());
+ map.resize(fbo.size());
+ map.setFramebufferObject(fbo.handle(), fbo.size());
map.setCoordinateZoom(QMapbox::Coordinate(60.170448, 24.942046), 14);
}
diff --git a/scripts/generate-shaders.js b/scripts/generate-shaders.js
index 46c097d51a..b1eeffb8a0 100755
--- a/scripts/generate-shaders.js
+++ b/scripts/generate-shaders.js
@@ -7,9 +7,6 @@ const outputPath = 'src/mbgl/shaders';
var shaders = require('../mapbox-gl-js/src/shaders');
-delete shaders.heatmap;
-delete shaders.heatmapTexture;
-
require('./style-code');
writeIfModified(path.join(outputPath, 'preludes.hpp'), `// NOTE: DO NOT CHANGE THIS FILE. IT IS AUTOMATICALLY GENERATED.
diff --git a/scripts/generate-style-code.js b/scripts/generate-style-code.js
index ff40244f98..6ddb787f19 100755
--- a/scripts/generate-style-code.js
+++ b/scripts/generate-style-code.js
@@ -97,6 +97,8 @@ global.paintPropertyType = function (property, type) {
global.propertyValueType = function (property) {
if (isDataDriven(property)) {
return `DataDrivenPropertyValue<${evaluatedType(property)}>`;
+ } else if (property.name === 'heatmap-color') {
+ return `HeatmapColorPropertyValue`;
} else {
return `PropertyValue<${evaluatedType(property)}>`;
}
diff --git a/scripts/style-spec.js b/scripts/style-spec.js
index 00daee70d0..196adc0b32 100644
--- a/scripts/style-spec.js
+++ b/scripts/style-spec.js
@@ -1,5 +1,3 @@
var spec = module.exports = require('../mapbox-gl-js/src/style-spec/reference/v8');
// Make temporary modifications here when Native doesn't have all features that JS has.
-
-delete spec.layer.type.values.heatmap;
diff --git a/src/mbgl/annotation/render_annotation_source.cpp b/src/mbgl/annotation/render_annotation_source.cpp
index 38ca5ccd0b..a237100d13 100644
--- a/src/mbgl/annotation/render_annotation_source.cpp
+++ b/src/mbgl/annotation/render_annotation_source.cpp
@@ -73,8 +73,8 @@ std::vector<Feature> RenderAnnotationSource::querySourceFeatures(const SourceQue
return {};
}
-void RenderAnnotationSource::onLowMemory() {
- tilePyramid.onLowMemory();
+void RenderAnnotationSource::reduceMemoryUse() {
+ tilePyramid.reduceMemoryUse();
}
void RenderAnnotationSource::dumpDebugLogs() const {
diff --git a/src/mbgl/annotation/render_annotation_source.hpp b/src/mbgl/annotation/render_annotation_source.hpp
index aa2578d4e3..e812ec2883 100644
--- a/src/mbgl/annotation/render_annotation_source.hpp
+++ b/src/mbgl/annotation/render_annotation_source.hpp
@@ -33,7 +33,7 @@ public:
std::vector<Feature>
querySourceFeatures(const SourceQueryOptions&) const final;
- void onLowMemory() final;
+ void reduceMemoryUse() final;
void dumpDebugLogs() const final;
private:
diff --git a/src/mbgl/geometry/dem_data.cpp b/src/mbgl/geometry/dem_data.cpp
index 78134dadc1..7fa98950ea 100644
--- a/src/mbgl/geometry/dem_data.cpp
+++ b/src/mbgl/geometry/dem_data.cpp
@@ -3,7 +3,7 @@
namespace mbgl {
-DEMData::DEMData(const PremultipliedImage& _image):
+DEMData::DEMData(const PremultipliedImage& _image, Tileset::DEMEncoding encoding):
dim(_image.size.height),
border(std::max<int32_t>(std::ceil(_image.size.height / 2), 1)),
stride(dim + 2 * border),
@@ -13,13 +13,25 @@ DEMData::DEMData(const PremultipliedImage& _image):
throw std::runtime_error("raster-dem tiles must be square.");
}
+ auto decodeMapbox = [] (const uint8_t r, const uint8_t g, const uint8_t b){
+ // https://www.mapbox.com/help/access-elevation-data/#mapbox-terrain-rgb
+ return (r * 256 * 256 + g * 256 + b)/10 - 10000;
+ };
+
+ auto decodeTerrarium = [] (const uint8_t r, const uint8_t g, const uint8_t b){
+ // https://aws.amazon.com/public-datasets/terrain/
+ return ((r * 256 + g + b / 256) - 32768);
+ };
+
+ auto decodeRGB = encoding == Tileset::DEMEncoding::Terrarium ? decodeTerrarium : decodeMapbox;
+
std::memset(image.data.get(), 0, image.bytes());
for (int32_t y = 0; y < dim; y++) {
for (int32_t x = 0; x < dim; x++) {
const int32_t i = y * dim + x;
const int32_t j = i * 4;
- set(x, y, (_image.data[j] * 256 * 256 + _image.data[j+1] * 256 + _image.data[j+2])/10 - 10000);
+ set(x, y, decodeRGB(_image.data[j], _image.data[j+1], _image.data[j+2]));
}
}
diff --git a/src/mbgl/geometry/dem_data.hpp b/src/mbgl/geometry/dem_data.hpp
index 507a51661d..817d3cc9c9 100644
--- a/src/mbgl/geometry/dem_data.hpp
+++ b/src/mbgl/geometry/dem_data.hpp
@@ -2,6 +2,7 @@
#include <mbgl/math/clamp.hpp>
#include <mbgl/util/image.hpp>
+#include <mbgl/util/tileset.hpp>
#include <memory>
#include <array>
@@ -12,7 +13,7 @@ namespace mbgl {
class DEMData {
public:
- DEMData(const PremultipliedImage& image);
+ DEMData(const PremultipliedImage& image, Tileset::DEMEncoding encoding);
void backfillBorder(const DEMData& borderTileData, int8_t dx, int8_t dy);
void set(const int32_t x, const int32_t y, const int32_t value) {
diff --git a/src/mbgl/gl/color_mode.hpp b/src/mbgl/gl/color_mode.hpp
index c6594a3a77..e394f43501 100644
--- a/src/mbgl/gl/color_mode.hpp
+++ b/src/mbgl/gl/color_mode.hpp
@@ -85,6 +85,10 @@ public:
static ColorMode alphaBlended() {
return ColorMode { Add { One, OneMinusSrcAlpha }, {}, { true, true, true, true } };
}
+
+ static ColorMode additive() {
+ return ColorMode { Add { One, One }, {}, { true, true, true, true } };
+ }
};
constexpr bool operator!=(const ColorMode::Mask& a, const ColorMode::Mask& b) {
diff --git a/src/mbgl/gl/context.cpp b/src/mbgl/gl/context.cpp
index 7444ac112c..ba44adb42b 100644
--- a/src/mbgl/gl/context.cpp
+++ b/src/mbgl/gl/context.cpp
@@ -60,6 +60,16 @@ static_assert(std::is_same<std::underlying_type_t<TextureFormat>, GLenum>::value
static_assert(underlying_type(TextureFormat::RGBA) == GL_RGBA, "OpenGL type mismatch");
static_assert(underlying_type(TextureFormat::Alpha) == GL_ALPHA, "OpenGL type mismatch");
+static_assert(std::is_same<std::underlying_type_t<TextureType>, GLenum>::value, "OpenGL type mismatch");
+static_assert(underlying_type(TextureType::UnsignedByte) == GL_UNSIGNED_BYTE, "OpenGL type mismatch");
+
+#if MBGL_USE_GLES2 && GL_HALF_FLOAT_OES
+static_assert(underlying_type(TextureType::HalfFloat) == GL_HALF_FLOAT_OES, "OpenGL type mismatch");
+#endif
+#if !MBGL_USE_GLES2 && GL_HALF_FLOAT_ARB
+static_assert(underlying_type(TextureType::HalfFloat) == GL_HALF_FLOAT_ARB, "OpenGL type mismatch");
+#endif
+
static_assert(underlying_type(UniformDataType::Float) == GL_FLOAT, "OpenGL type mismatch");
static_assert(underlying_type(UniformDataType::FloatVec2) == GL_FLOAT_VEC2, "OpenGL type mismatch");
static_assert(underlying_type(UniformDataType::FloatVec3) == GL_FLOAT_VEC3, "OpenGL type mismatch");
@@ -116,6 +126,19 @@ void Context::initializeExtensions(const std::function<gl::ProcAddress(const cha
programBinary = std::make_unique<extension::ProgramBinary>(fn);
#endif
+#if MBGL_USE_GLES2
+ constexpr const char* halfFloatExtensionName = "OES_texture_half_float";
+ constexpr const char* halfFloatColorBufferExtensionName = "EXT_color_buffer_half_float";
+#else
+ constexpr const char* halfFloatExtensionName = "ARB_half_float_pixel";
+ constexpr const char* halfFloatColorBufferExtensionName = "ARB_color_buffer_float";
+#endif
+ if (strstr(extensions, halfFloatExtensionName) != nullptr &&
+ strstr(extensions, halfFloatColorBufferExtensionName) != nullptr) {
+
+ supportsHalfFloatTextures = true;
+ }
+
if (!supportsVertexArrays()) {
Log::Warning(Event::OpenGL, "Not using Vertex Array Objects");
}
@@ -258,9 +281,17 @@ UniqueTexture Context::createTexture() {
bool Context::supportsVertexArrays() const {
static bool blacklisted = []() {
- // Blacklist Adreno 3xx as it crashes on glBuffer(Sub)Data
const std::string renderer = reinterpret_cast<const char*>(glGetString(GL_RENDERER));
- return renderer.find("Adreno (TM) 3") != std::string::npos;
+
+ Log::Info(Event::General, "GPU Identifier: %s", renderer.c_str());
+
+ // Blacklist Adreno 2xx, 3xx as it crashes on glBuffer(Sub)Data
+ // Blacklist ARM Mali-T720 (in some MT8163 chipsets) as it crashes on glBindVertexArray
+ return renderer.find("Adreno (TM) 2") != std::string::npos
+ || renderer.find("Adreno (TM) 3") != std::string::npos
+ || renderer.find("Mali-T720") != std::string::npos
+ || renderer.find("Sapphire 650") != std::string::npos;
+
}();
return !blacklisted &&
@@ -490,10 +521,10 @@ Context::createFramebuffer(const Texture& color,
}
UniqueTexture
-Context::createTexture(const Size size, const void* data, TextureFormat format, TextureUnit unit) {
+Context::createTexture(const Size size, const void* data, TextureFormat format, TextureUnit unit, TextureType type) {
auto obj = createTexture();
pixelStoreUnpack = { 1 };
- updateTexture(obj, size, data, format, unit);
+ updateTexture(obj, size, data, format, unit, type);
// We are using clamp to edge here since OpenGL ES doesn't allow GL_REPEAT on NPOT textures.
// We use those when the pixelRatio isn't a power of two, e.g. on iPhone 6 Plus.
MBGL_CHECK_ERROR(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
@@ -504,11 +535,11 @@ Context::createTexture(const Size size, const void* data, TextureFormat format,
}
void Context::updateTexture(
- TextureID id, const Size size, const void* data, TextureFormat format, TextureUnit unit) {
+ TextureID id, const Size size, const void* data, TextureFormat format, TextureUnit unit, TextureType type) {
activeTextureUnit = unit;
texture[unit] = id;
MBGL_CHECK_ERROR(glTexImage2D(GL_TEXTURE_2D, 0, static_cast<GLenum>(format), size.width,
- size.height, 0, static_cast<GLenum>(format), GL_UNSIGNED_BYTE,
+ size.height, 0, static_cast<GLenum>(format), static_cast<GLenum>(type),
data));
}
diff --git a/src/mbgl/gl/context.hpp b/src/mbgl/gl/context.hpp
index 14f078367f..67624288e2 100644
--- a/src/mbgl/gl/context.hpp
+++ b/src/mbgl/gl/context.hpp
@@ -125,23 +125,29 @@ public:
// Create a texture from an image with data.
template <typename Image>
- Texture createTexture(const Image& image, TextureUnit unit = 0) {
+ Texture createTexture(const Image& image,
+ TextureUnit unit = 0,
+ TextureType type = TextureType::UnsignedByte) {
auto format = image.channels == 4 ? TextureFormat::RGBA : TextureFormat::Alpha;
- return { image.size, createTexture(image.size, image.data.get(), format, unit) };
+ return { image.size, createTexture(image.size, image.data.get(), format, unit, type) };
}
template <typename Image>
- void updateTexture(Texture& obj, const Image& image, TextureUnit unit = 0) {
+ void updateTexture(Texture& obj,
+ const Image& image,
+ TextureUnit unit = 0,
+ TextureType type = TextureType::UnsignedByte) {
auto format = image.channels == 4 ? TextureFormat::RGBA : TextureFormat::Alpha;
- updateTexture(obj.texture.get(), image.size, image.data.get(), format, unit);
+ updateTexture(obj.texture.get(), image.size, image.data.get(), format, unit, type);
obj.size = image.size;
}
// Creates an empty texture with the specified dimensions.
Texture createTexture(const Size size,
TextureFormat format = TextureFormat::RGBA,
- TextureUnit unit = 0) {
- return { size, createTexture(size, nullptr, format, unit) };
+ TextureUnit unit = 0,
+ TextureType type = TextureType::UnsignedByte) {
+ return { size, createTexture(size, nullptr, format, unit, type) };
}
void bindTexture(Texture&,
@@ -232,6 +238,8 @@ public:
State<value::PixelTransferStencil> pixelTransferStencil;
#endif // MBGL_USE_GLES2
+ bool supportsHalfFloatTextures = false;
+
private:
State<value::StencilFunc> stencilFunc;
State<value::StencilMask> stencilMask;
@@ -259,8 +267,8 @@ private:
void updateVertexBuffer(UniqueBuffer& buffer, const void* data, std::size_t size);
UniqueBuffer createIndexBuffer(const void* data, std::size_t size, const BufferUsage usage);
void updateIndexBuffer(UniqueBuffer& buffer, const void* data, std::size_t size);
- UniqueTexture createTexture(Size size, const void* data, TextureFormat, TextureUnit);
- void updateTexture(TextureID, Size size, const void* data, TextureFormat, TextureUnit);
+ UniqueTexture createTexture(Size size, const void* data, TextureFormat, TextureUnit, TextureType);
+ void updateTexture(TextureID, Size size, const void* data, TextureFormat, TextureUnit, TextureType);
UniqueFramebuffer createFramebuffer();
UniqueRenderbuffer createRenderbuffer(RenderbufferType, Size size);
std::unique_ptr<uint8_t[]> readFramebuffer(Size, TextureFormat, bool flip);
diff --git a/src/mbgl/gl/types.hpp b/src/mbgl/gl/types.hpp
index da08195e58..376a784a0c 100644
--- a/src/mbgl/gl/types.hpp
+++ b/src/mbgl/gl/types.hpp
@@ -64,6 +64,15 @@ enum class TextureFormat : uint32_t {
#endif // MBGL_USE_GLES2
};
+enum class TextureType : uint32_t {
+ UnsignedByte = 0x1401,
+#if MBGL_USE_GLES2
+ HalfFloat = 0x8D61,
+#else
+ HalfFloat = 0x140B,
+#endif // MBGL_USE_GLES2
+};
+
enum class PrimitiveType {
Points = 0x0000,
Lines = 0x0001,
diff --git a/src/mbgl/layout/symbol_projection.cpp b/src/mbgl/layout/symbol_projection.cpp
index ee6385c93c..9e077e2532 100644
--- a/src/mbgl/layout/symbol_projection.cpp
+++ b/src/mbgl/layout/symbol_projection.cpp
@@ -318,7 +318,7 @@ namespace mbgl {
placedGlyphs.push_back(*placedGlyph);
}
placedGlyphs.push_back(firstAndLastGlyph->second);
- } else {
+ } else if (symbol.glyphOffsets.size() == 1) {
// Only a single glyph to place
// So, determine whether to flip based on projected angle of the line segment it's on
if (keepUpright && !flip) {
@@ -337,7 +337,6 @@ namespace mbgl {
return *orientationChange;
}
}
- assert(symbol.glyphOffsets.size() == 1); // We are relying on SymbolInstance.hasText filtering out symbols without any glyphs at all
const float glyphOffsetX = symbol.glyphOffsets.front();
optional<PlacedGlyph> singleGlyph = placeGlyphAlongLine(fontScale * glyphOffsetX, lineOffsetX, lineOffsetY, flip, projectedAnchorPoint, symbol.anchorPoint, symbol.segment,
symbol.line, symbol.tileDistances, labelPlaneMatrix, false);
@@ -347,6 +346,8 @@ namespace mbgl {
placedGlyphs.push_back(*singleGlyph);
}
+ // The number of placedGlyphs must equal the number of glyphOffsets, which must correspond to the number of glyph vertices
+ // There may be 0 glyphs here, if a label consists entirely of glyphs that have 0x0 dimensions
for (auto& placedGlyph : placedGlyphs) {
addDynamicAttributes(placedGlyph.point, placedGlyph.angle, dynamicVertexArray);
}
diff --git a/src/mbgl/programs/attributes.hpp b/src/mbgl/programs/attributes.hpp
index 5d7a6474cf..c677c84d5d 100644
--- a/src/mbgl/programs/attributes.hpp
+++ b/src/mbgl/programs/attributes.hpp
@@ -30,7 +30,7 @@ MBGL_DEFINE_ATTRIBUTE(int16_t, 2, a_anchor_pos);
MBGL_DEFINE_ATTRIBUTE(uint16_t, 2, a_texture_pos);
MBGL_DEFINE_ATTRIBUTE(int16_t, 4, a_normal_ed);
MBGL_DEFINE_ATTRIBUTE(uint8_t, 1, a_fade_opacity);
-MBGL_DEFINE_ATTRIBUTE(uint8_t, 2, a_placed);
+MBGL_DEFINE_ATTRIBUTE(uint8_t, 2, a_placed);
template <typename T, std::size_t N>
struct a_data {
@@ -142,6 +142,11 @@ struct a_halo_blur {
using Type = gl::Attribute<float, 1>;
};
+struct a_weight {
+ static auto name() { return "a_weight"; }
+ using Type = gl::Attribute<float, 1>;
+};
+
} // namespace attributes
struct PositionOnlyLayoutAttributes : gl::Attributes<
diff --git a/src/mbgl/programs/collision_box_program.hpp b/src/mbgl/programs/collision_box_program.hpp
index 8d712a3df3..6e75adf36e 100644
--- a/src/mbgl/programs/collision_box_program.hpp
+++ b/src/mbgl/programs/collision_box_program.hpp
@@ -110,6 +110,7 @@ class CollisionCircleProgram : public Program<
gl::Uniforms<
uniforms::u_matrix,
uniforms::u_extrude_scale,
+ uniforms::u_overscale_factor,
uniforms::u_camera_to_center_distance>,
style::Properties<>>
{
diff --git a/src/mbgl/programs/heatmap_program.cpp b/src/mbgl/programs/heatmap_program.cpp
new file mode 100644
index 0000000000..67f84fbd52
--- /dev/null
+++ b/src/mbgl/programs/heatmap_program.cpp
@@ -0,0 +1,7 @@
+#include <mbgl/programs/heatmap_program.hpp>
+
+namespace mbgl {
+
+static_assert(sizeof(HeatmapLayoutVertex) == 4, "expected HeatmapLayoutVertex size");
+
+} // namespace mbgl
diff --git a/src/mbgl/programs/heatmap_program.hpp b/src/mbgl/programs/heatmap_program.hpp
new file mode 100644
index 0000000000..2d9b80404f
--- /dev/null
+++ b/src/mbgl/programs/heatmap_program.hpp
@@ -0,0 +1,49 @@
+#pragma once
+
+#include <mbgl/programs/program.hpp>
+#include <mbgl/programs/attributes.hpp>
+#include <mbgl/programs/uniforms.hpp>
+#include <mbgl/shaders/heatmap.hpp>
+#include <mbgl/util/geometry.hpp>
+#include <mbgl/style/layers/heatmap_layer_properties.hpp>
+
+namespace mbgl {
+
+namespace uniforms {
+MBGL_DEFINE_UNIFORM_SCALAR(float, u_intensity);
+} // namespace uniforms
+
+class HeatmapProgram : public Program<
+ shaders::heatmap,
+ gl::Triangle,
+ gl::Attributes<
+ attributes::a_pos>,
+ gl::Uniforms<
+ uniforms::u_intensity,
+ uniforms::u_matrix,
+ uniforms::heatmap::u_extrude_scale>,
+ style::HeatmapPaintProperties>
+{
+public:
+ using Program::Program;
+
+ /*
+ * @param {number} x vertex position
+ * @param {number} y vertex position
+ * @param {number} ex extrude normal
+ * @param {number} ey extrude normal
+ */
+ static LayoutVertex vertex(Point<int16_t> p, float ex, float ey) {
+ return LayoutVertex {
+ {{
+ static_cast<int16_t>((p.x * 2) + ((ex + 1) / 2)),
+ static_cast<int16_t>((p.y * 2) + ((ey + 1) / 2))
+ }}
+ };
+ }
+};
+
+using HeatmapLayoutVertex = HeatmapProgram::LayoutVertex;
+using HeatmapAttributes = HeatmapProgram::Attributes;
+
+} // namespace mbgl
diff --git a/src/mbgl/programs/heatmap_texture_program.cpp b/src/mbgl/programs/heatmap_texture_program.cpp
new file mode 100644
index 0000000000..3b0e24eab8
--- /dev/null
+++ b/src/mbgl/programs/heatmap_texture_program.cpp
@@ -0,0 +1,7 @@
+#include <mbgl/programs/heatmap_texture_program.hpp>
+
+namespace mbgl {
+
+static_assert(sizeof(HeatmapTextureLayoutVertex) == 4, "expected HeatmapTextureLayoutVertex size");
+
+} // namespace mbgl
diff --git a/src/mbgl/programs/heatmap_texture_program.hpp b/src/mbgl/programs/heatmap_texture_program.hpp
new file mode 100644
index 0000000000..7afe8060d0
--- /dev/null
+++ b/src/mbgl/programs/heatmap_texture_program.hpp
@@ -0,0 +1,43 @@
+#pragma once
+
+#include <mbgl/programs/program.hpp>
+#include <mbgl/programs/attributes.hpp>
+#include <mbgl/programs/uniforms.hpp>
+#include <mbgl/shaders/heatmap_texture.hpp>
+#include <mbgl/style/properties.hpp>
+#include <mbgl/util/geometry.hpp>
+
+namespace mbgl {
+
+namespace uniforms {
+MBGL_DEFINE_UNIFORM_SCALAR(gl::TextureUnit, u_color_ramp);
+} // namespace uniforms
+
+class HeatmapTextureProgram : public Program<
+ shaders::heatmap_texture,
+ gl::Triangle,
+ gl::Attributes<attributes::a_pos>,
+ gl::Uniforms<
+ uniforms::u_matrix,
+ uniforms::u_world,
+ uniforms::u_image,
+ uniforms::u_color_ramp,
+ uniforms::u_opacity>,
+ style::Properties<>> {
+public:
+ using Program::Program;
+
+ static LayoutVertex layoutVertex(Point<int16_t> p) {
+ return LayoutVertex{
+ {{
+ p.x,
+ p.y
+ }}
+ };
+ }
+};
+
+using HeatmapTextureLayoutVertex = HeatmapTextureProgram::LayoutVertex;
+using HeatmapTextureAttributes = HeatmapTextureProgram::Attributes;
+
+} // namespace mbgl
diff --git a/src/mbgl/programs/hillshade_prepare_program.hpp b/src/mbgl/programs/hillshade_prepare_program.hpp
index 0f31a22df5..76638afddd 100644
--- a/src/mbgl/programs/hillshade_prepare_program.hpp
+++ b/src/mbgl/programs/hillshade_prepare_program.hpp
@@ -10,6 +10,7 @@ namespace mbgl {
namespace uniforms {
MBGL_DEFINE_UNIFORM_VECTOR(uint16_t, 2, u_dimension);
+MBGL_DEFINE_UNIFORM_SCALAR(float, u_maxzoom);
} // namespace uniforms
class HillshadePrepareProgram : public Program<
@@ -22,6 +23,7 @@ class HillshadePrepareProgram : public Program<
uniforms::u_matrix,
uniforms::u_dimension,
uniforms::u_zoom,
+ uniforms::u_maxzoom,
uniforms::u_image>,
style::Properties<>> {
public:
diff --git a/src/mbgl/programs/programs.hpp b/src/mbgl/programs/programs.hpp
index f533a6f633..b703323d9c 100644
--- a/src/mbgl/programs/programs.hpp
+++ b/src/mbgl/programs/programs.hpp
@@ -6,6 +6,8 @@
#include <mbgl/programs/extrusion_texture_program.hpp>
#include <mbgl/programs/fill_program.hpp>
#include <mbgl/programs/fill_extrusion_program.hpp>
+#include <mbgl/programs/heatmap_program.hpp>
+#include <mbgl/programs/heatmap_texture_program.hpp>
#include <mbgl/programs/hillshade_program.hpp>
#include <mbgl/programs/hillshade_prepare_program.hpp>
#include <mbgl/programs/line_program.hpp>
@@ -30,6 +32,8 @@ public:
fillPattern(context, programParameters),
fillOutline(context, programParameters),
fillOutlinePattern(context, programParameters),
+ heatmap(context, programParameters),
+ heatmapTexture(context, programParameters),
hillshade(context, programParameters),
hillshadePrepare(context, programParameters),
line(context, programParameters),
@@ -55,6 +59,8 @@ public:
ProgramMap<FillPatternProgram> fillPattern;
ProgramMap<FillOutlineProgram> fillOutline;
ProgramMap<FillOutlinePatternProgram> fillOutlinePattern;
+ ProgramMap<HeatmapProgram> heatmap;
+ HeatmapTextureProgram heatmapTexture;
HillshadeProgram hillshade;
HillshadePrepareProgram hillshadePrepare;
ProgramMap<LineProgram> line;
diff --git a/src/mbgl/programs/uniforms.hpp b/src/mbgl/programs/uniforms.hpp
index 184f42e504..071a27c808 100644
--- a/src/mbgl/programs/uniforms.hpp
+++ b/src/mbgl/programs/uniforms.hpp
@@ -37,9 +37,14 @@ MBGL_DEFINE_UNIFORM_SCALAR(Size, u_texsize);
MBGL_DEFINE_UNIFORM_SCALAR(bool, u_pitch_with_map);
MBGL_DEFINE_UNIFORM_SCALAR(float, u_camera_to_center_distance);
MBGL_DEFINE_UNIFORM_SCALAR(float, u_fade_change);
+MBGL_DEFINE_UNIFORM_SCALAR(float, u_weight);
MBGL_DEFINE_UNIFORM_VECTOR(float, 2, u_extrude_scale);
+namespace heatmap {
+MBGL_DEFINE_UNIFORM_SCALAR(float, u_extrude_scale);
+} // namespace heatmap
+
MBGL_DEFINE_UNIFORM_VECTOR(uint16_t, 2, u_pattern_tl_a);
MBGL_DEFINE_UNIFORM_VECTOR(uint16_t, 2, u_pattern_br_a);
MBGL_DEFINE_UNIFORM_VECTOR(uint16_t, 2, u_pattern_tl_b);
@@ -55,6 +60,7 @@ MBGL_DEFINE_UNIFORM_SCALAR(gl::TextureUnit, u_fadetexture);
MBGL_DEFINE_UNIFORM_SCALAR(float, u_scale_a);
MBGL_DEFINE_UNIFORM_SCALAR(float, u_scale_b);
MBGL_DEFINE_UNIFORM_SCALAR(float, u_tile_units_to_pixels);
+MBGL_DEFINE_UNIFORM_SCALAR(float, u_overscale_factor);
} // namespace uniforms
} // namespace mbgl
diff --git a/src/mbgl/renderer/buckets/heatmap_bucket.cpp b/src/mbgl/renderer/buckets/heatmap_bucket.cpp
new file mode 100644
index 0000000000..a185e04ad2
--- /dev/null
+++ b/src/mbgl/renderer/buckets/heatmap_bucket.cpp
@@ -0,0 +1,98 @@
+#include <mbgl/renderer/buckets/heatmap_bucket.hpp>
+#include <mbgl/renderer/bucket_parameters.hpp>
+#include <mbgl/programs/heatmap_program.hpp>
+#include <mbgl/style/layers/heatmap_layer_impl.hpp>
+#include <mbgl/renderer/layers/render_heatmap_layer.hpp>
+#include <mbgl/util/constants.hpp>
+#include <mbgl/util/math.hpp>
+
+namespace mbgl {
+
+using namespace style;
+
+HeatmapBucket::HeatmapBucket(const BucketParameters& parameters, const std::vector<const RenderLayer*>& layers)
+ : mode(parameters.mode) {
+ for (const auto& layer : layers) {
+ paintPropertyBinders.emplace(
+ std::piecewise_construct,
+ std::forward_as_tuple(layer->getID()),
+ std::forward_as_tuple(
+ layer->as<RenderHeatmapLayer>()->evaluated,
+ parameters.tileID.overscaledZ));
+ }
+}
+
+void HeatmapBucket::upload(gl::Context& context) {
+ vertexBuffer = context.createVertexBuffer(std::move(vertices));
+ indexBuffer = context.createIndexBuffer(std::move(triangles));
+
+ for (auto& pair : paintPropertyBinders) {
+ pair.second.upload(context);
+ }
+
+ uploaded = true;
+}
+
+bool HeatmapBucket::hasData() const {
+ return !segments.empty();
+}
+
+void HeatmapBucket::addFeature(const GeometryTileFeature& feature,
+ const GeometryCollection& geometry) {
+ constexpr const uint16_t vertexLength = 4;
+
+ for (auto& points : geometry) {
+ for(auto& point : points) {
+ auto x = point.x;
+ auto y = point.y;
+
+ // Do not include points that are outside the tile boundaries.
+ // Include all points in Still mode. You need to include points from
+ // neighbouring tiles so that they are not clipped at tile boundaries.
+ if ((mode == MapMode::Continuous) &&
+ (x < 0 || x >= util::EXTENT || y < 0 || y >= util::EXTENT)) continue;
+
+ if (segments.empty() || segments.back().vertexLength + vertexLength > std::numeric_limits<uint16_t>::max()) {
+ // Move to a new segments because the old one can't hold the geometry.
+ segments.emplace_back(vertices.vertexSize(), triangles.indexSize());
+ }
+
+ // this geometry will be of the Point type, and we'll derive
+ // two triangles from it.
+ //
+ // ┌─────────┐
+ // │ 4 3 │
+ // │ │
+ // │ 1 2 │
+ // └─────────┘
+ //
+ vertices.emplace_back(HeatmapProgram::vertex(point, -1, -1)); // 1
+ vertices.emplace_back(HeatmapProgram::vertex(point, 1, -1)); // 2
+ vertices.emplace_back(HeatmapProgram::vertex(point, 1, 1)); // 3
+ vertices.emplace_back(HeatmapProgram::vertex(point, -1, 1)); // 4
+
+ auto& segment = segments.back();
+ assert(segment.vertexLength <= std::numeric_limits<uint16_t>::max());
+ uint16_t index = segment.vertexLength;
+
+ // 1, 2, 3
+ // 1, 4, 3
+ triangles.emplace_back(index, index + 1, index + 2);
+ triangles.emplace_back(index, index + 3, index + 2);
+
+ segment.vertexLength += vertexLength;
+ segment.indexLength += 6;
+ }
+ }
+
+ for (auto& pair : paintPropertyBinders) {
+ pair.second.populateVertexVectors(feature, vertices.vertexSize());
+ }
+}
+
+float HeatmapBucket::getQueryRadius(const RenderLayer& layer) const {
+ (void)layer;
+ return 0;
+}
+
+} // namespace mbgl
diff --git a/src/mbgl/renderer/buckets/heatmap_bucket.hpp b/src/mbgl/renderer/buckets/heatmap_bucket.hpp
new file mode 100644
index 0000000000..3b9f1edb81
--- /dev/null
+++ b/src/mbgl/renderer/buckets/heatmap_bucket.hpp
@@ -0,0 +1,40 @@
+#pragma once
+
+#include <mbgl/renderer/bucket.hpp>
+#include <mbgl/map/mode.hpp>
+#include <mbgl/tile/geometry_tile_data.hpp>
+#include <mbgl/gl/vertex_buffer.hpp>
+#include <mbgl/gl/index_buffer.hpp>
+#include <mbgl/programs/segment.hpp>
+#include <mbgl/programs/heatmap_program.hpp>
+#include <mbgl/style/layers/heatmap_layer_properties.hpp>
+
+namespace mbgl {
+
+class BucketParameters;
+
+class HeatmapBucket : public Bucket {
+public:
+ HeatmapBucket(const BucketParameters&, const std::vector<const RenderLayer*>&);
+
+ void addFeature(const GeometryTileFeature&,
+ const GeometryCollection&) override;
+ bool hasData() const override;
+
+ void upload(gl::Context&) override;
+
+ float getQueryRadius(const RenderLayer&) const override;
+
+ gl::VertexVector<HeatmapLayoutVertex> vertices;
+ gl::IndexVector<gl::Triangles> triangles;
+ SegmentVector<HeatmapAttributes> segments;
+
+ optional<gl::VertexBuffer<HeatmapLayoutVertex>> vertexBuffer;
+ optional<gl::IndexBuffer<gl::Triangles>> indexBuffer;
+
+ std::map<std::string, HeatmapProgram::PaintPropertyBinders> paintPropertyBinders;
+
+ const MapMode mode;
+};
+
+} // namespace mbgl
diff --git a/src/mbgl/renderer/buckets/hillshade_bucket.cpp b/src/mbgl/renderer/buckets/hillshade_bucket.cpp
index 8011681ee0..00b9536894 100644
--- a/src/mbgl/renderer/buckets/hillshade_bucket.cpp
+++ b/src/mbgl/renderer/buckets/hillshade_bucket.cpp
@@ -8,7 +8,7 @@ namespace mbgl {
using namespace style;
-HillshadeBucket::HillshadeBucket(PremultipliedImage&& image_): demdata(image_) {
+HillshadeBucket::HillshadeBucket(PremultipliedImage&& image_, Tileset::DEMEncoding encoding): demdata(image_, encoding) {
}
HillshadeBucket::HillshadeBucket(DEMData&& demdata_) : demdata(std::move(demdata_)) {
diff --git a/src/mbgl/renderer/buckets/hillshade_bucket.hpp b/src/mbgl/renderer/buckets/hillshade_bucket.hpp
index 3d9f6c61af..5335f7ceda 100644
--- a/src/mbgl/renderer/buckets/hillshade_bucket.hpp
+++ b/src/mbgl/renderer/buckets/hillshade_bucket.hpp
@@ -8,6 +8,7 @@
#include <mbgl/renderer/bucket.hpp>
#include <mbgl/renderer/tile_mask.hpp>
#include <mbgl/geometry/dem_data.hpp>
+#include <mbgl/util/tileset.hpp>
#include <mbgl/util/image.hpp>
#include <mbgl/util/mat4.hpp>
#include <mbgl/util/optional.hpp>
@@ -16,8 +17,8 @@ namespace mbgl {
class HillshadeBucket : public Bucket {
public:
- HillshadeBucket(PremultipliedImage&&);
- HillshadeBucket(std::shared_ptr<PremultipliedImage>);
+ HillshadeBucket(PremultipliedImage&&, Tileset::DEMEncoding encoding);
+ HillshadeBucket(std::shared_ptr<PremultipliedImage>, Tileset::DEMEncoding encoding);
HillshadeBucket(DEMData&&);
diff --git a/src/mbgl/renderer/layers/render_custom_layer.cpp b/src/mbgl/renderer/layers/render_custom_layer.cpp
index adafd8583f..a429b8d82e 100644
--- a/src/mbgl/renderer/layers/render_custom_layer.cpp
+++ b/src/mbgl/renderer/layers/render_custom_layer.cpp
@@ -5,6 +5,7 @@
#include <mbgl/renderer/bucket.hpp>
#include <mbgl/style/layers/custom_layer_impl.hpp>
#include <mbgl/map/transform_state.hpp>
+#include <mbgl/gl/gl.hpp>
namespace mbgl {
@@ -46,11 +47,11 @@ void RenderCustomLayer::render(PaintParameters& paintParameters, RenderSource*)
if (context != impl().context || !initialized) {
//If the context changed, deinitialize the previous one before initializing the new one.
if (context && !contextDestroyed && impl().deinitializeFn) {
- impl().deinitializeFn(context);
+ MBGL_CHECK_ERROR(impl().deinitializeFn(context));
}
context = impl().context;
assert(impl().initializeFn);
- impl().initializeFn(impl().context);
+ MBGL_CHECK_ERROR(impl().initializeFn(impl().context));
initialized = true;
}
@@ -75,7 +76,7 @@ void RenderCustomLayer::render(PaintParameters& paintParameters, RenderSource*)
parameters.fieldOfView = state.getFieldOfView();
assert(impl().renderFn);
- impl().renderFn(context, parameters);
+ MBGL_CHECK_ERROR(impl().renderFn(context, parameters));
// Reset the view back to our original one, just in case the CustomLayer changed
// the viewport or Framebuffer.
diff --git a/src/mbgl/renderer/layers/render_heatmap_layer.cpp b/src/mbgl/renderer/layers/render_heatmap_layer.cpp
new file mode 100644
index 0000000000..4f2e899220
--- /dev/null
+++ b/src/mbgl/renderer/layers/render_heatmap_layer.cpp
@@ -0,0 +1,178 @@
+#include <mbgl/renderer/layers/render_heatmap_layer.hpp>
+#include <mbgl/renderer/buckets/heatmap_bucket.hpp>
+#include <mbgl/renderer/render_tile.hpp>
+#include <mbgl/renderer/paint_parameters.hpp>
+#include <mbgl/renderer/render_static_data.hpp>
+#include <mbgl/programs/programs.hpp>
+#include <mbgl/programs/heatmap_program.hpp>
+#include <mbgl/tile/tile.hpp>
+#include <mbgl/style/layers/heatmap_layer.hpp>
+#include <mbgl/style/layers/heatmap_layer_impl.hpp>
+#include <mbgl/geometry/feature_index.hpp>
+#include <mbgl/util/math.hpp>
+#include <mbgl/util/intersection_tests.hpp>
+
+namespace mbgl {
+
+using namespace style;
+
+RenderHeatmapLayer::RenderHeatmapLayer(Immutable<style::HeatmapLayer::Impl> _impl)
+ : RenderLayer(style::LayerType::Heatmap, _impl),
+ unevaluated(impl().paint.untransitioned()), colorRamp({256, 1}) {
+}
+
+const style::HeatmapLayer::Impl& RenderHeatmapLayer::impl() const {
+ return static_cast<const style::HeatmapLayer::Impl&>(*baseImpl);
+}
+
+std::unique_ptr<Bucket> RenderHeatmapLayer::createBucket(const BucketParameters& parameters, const std::vector<const RenderLayer*>& layers) const {
+ return std::make_unique<HeatmapBucket>(parameters, layers);
+}
+
+void RenderHeatmapLayer::transition(const TransitionParameters& parameters) {
+ unevaluated = impl().paint.transitioned(parameters, std::move(unevaluated));
+}
+
+void RenderHeatmapLayer::evaluate(const PropertyEvaluationParameters& parameters) {
+ evaluated = unevaluated.evaluate(parameters);
+
+ passes = (evaluated.get<style::HeatmapOpacity>() > 0)
+ ? (RenderPass::Translucent | RenderPass::Pass3D)
+ : RenderPass::None;
+}
+
+bool RenderHeatmapLayer::hasTransition() const {
+ return unevaluated.hasTransition();
+}
+
+void RenderHeatmapLayer::render(PaintParameters& parameters, RenderSource*) {
+ if (parameters.pass == RenderPass::Opaque) {
+ return;
+ }
+
+ if (parameters.pass == RenderPass::Pass3D) {
+ const auto& viewportSize = parameters.staticData.backendSize;
+ const auto size = Size{viewportSize.width / 4, viewportSize.height / 4};
+
+ if (!renderTexture || renderTexture->getSize() != size) {
+ if (parameters.context.supportsHalfFloatTextures) {
+ renderTexture = OffscreenTexture(parameters.context, size, gl::TextureType::HalfFloat);
+
+ try {
+ renderTexture->bind();
+ } catch (const std::runtime_error& ex) {
+ // can't render to a half-float texture; falling back to unsigned byte one
+ renderTexture = nullopt;
+ parameters.context.supportsHalfFloatTextures = false;
+ }
+ }
+
+ if (!parameters.context.supportsHalfFloatTextures || !renderTexture) {
+ renderTexture = OffscreenTexture(parameters.context, size, gl::TextureType::UnsignedByte);
+ renderTexture->bind();
+ }
+
+ } else {
+ renderTexture->bind();
+ }
+
+ if (!colorRampTexture) {
+ colorRampTexture = parameters.context.createTexture(colorRamp, 1, gl::TextureType::UnsignedByte);
+ }
+
+ parameters.context.clear(Color{ 0.0f, 0.0f, 0.0f, 1.0f }, {}, {});
+
+ for (const RenderTile& tile : renderTiles) {
+ assert(dynamic_cast<HeatmapBucket*>(tile.tile.getBucket(*baseImpl)));
+ HeatmapBucket& bucket = *reinterpret_cast<HeatmapBucket*>(tile.tile.getBucket(*baseImpl));
+
+ const auto extrudeScale = tile.id.pixelsToTileUnits(1, parameters.state.getZoom());
+
+ const auto stencilMode = parameters.mapMode != MapMode::Continuous
+ ? parameters.stencilModeForClipping(tile.clip)
+ : gl::StencilMode::disabled();
+
+ parameters.programs.heatmap.get(evaluated).draw(
+ parameters.context,
+ gl::Triangles(),
+ parameters.depthModeForSublayer(0, gl::DepthMode::ReadOnly),
+ stencilMode,
+ gl::ColorMode::additive(),
+ HeatmapProgram::UniformValues {
+ uniforms::u_intensity::Value{evaluated.get<style::HeatmapIntensity>()},
+ uniforms::u_matrix::Value{tile.matrix},
+ uniforms::heatmap::u_extrude_scale::Value{extrudeScale}
+ },
+ *bucket.vertexBuffer,
+ *bucket.indexBuffer,
+ bucket.segments,
+ bucket.paintPropertyBinders.at(getID()),
+ evaluated,
+ parameters.state.getZoom(),
+ getID()
+ );
+ }
+
+ } else if (parameters.pass == RenderPass::Translucent) {
+ parameters.context.bindTexture(renderTexture->getTexture(), 0, gl::TextureFilter::Linear);
+ parameters.context.bindTexture(*colorRampTexture, 1, gl::TextureFilter::Linear);
+
+ const auto& size = parameters.staticData.backendSize;
+
+ mat4 viewportMat;
+ matrix::ortho(viewportMat, 0, size.width, size.height, 0, 0, 1);
+
+ const Properties<>::PossiblyEvaluated properties;
+
+ parameters.programs.heatmapTexture.draw(
+ parameters.context, gl::Triangles(), gl::DepthMode::disabled(),
+ gl::StencilMode::disabled(), parameters.colorModeForRenderPass(),
+ HeatmapTextureProgram::UniformValues{
+ uniforms::u_matrix::Value{ viewportMat }, uniforms::u_world::Value{ size },
+ uniforms::u_image::Value{ 0 },
+ uniforms::u_color_ramp::Value{ 1 },
+ uniforms::u_opacity::Value{ evaluated.get<HeatmapOpacity>() } },
+ parameters.staticData.extrusionTextureVertexBuffer,
+ parameters.staticData.quadTriangleIndexBuffer,
+ parameters.staticData.extrusionTextureSegments,
+ HeatmapTextureProgram::PaintPropertyBinders{ properties, 0 }, properties,
+ parameters.state.getZoom(), getID());
+ }
+}
+
+void RenderHeatmapLayer::updateColorRamp() {
+ auto colorValue = unevaluated.get<HeatmapColor>().getValue();
+ if (colorValue.isUndefined()) {
+ colorValue = HeatmapLayer::getDefaultHeatmapColor();
+ }
+
+ const auto length = colorRamp.bytes();
+
+ for (uint32_t i = 0; i < length; i += 4) {
+ const auto color = colorValue.evaluate(static_cast<double>(i) / length);
+ colorRamp.data[i + 0] = std::floor(color.r * 255);
+ colorRamp.data[i + 1] = std::floor(color.g * 255);
+ colorRamp.data[i + 2] = std::floor(color.b * 255);
+ colorRamp.data[i + 3] = std::floor(color.a * 255);
+ }
+
+ if (colorRampTexture) {
+ colorRampTexture = nullopt;
+ }
+}
+
+bool RenderHeatmapLayer::queryIntersectsFeature(
+ const GeometryCoordinates& queryGeometry,
+ const GeometryTileFeature& feature,
+ const float zoom,
+ const float bearing,
+ const float pixelsToTileUnits) const {
+ (void) queryGeometry;
+ (void) feature;
+ (void) zoom;
+ (void) bearing;
+ (void) pixelsToTileUnits;
+ return false;
+}
+
+} // namespace mbgl
diff --git a/src/mbgl/renderer/layers/render_heatmap_layer.hpp b/src/mbgl/renderer/layers/render_heatmap_layer.hpp
new file mode 100644
index 0000000000..3f0b1f91b4
--- /dev/null
+++ b/src/mbgl/renderer/layers/render_heatmap_layer.hpp
@@ -0,0 +1,48 @@
+#pragma once
+
+#include <mbgl/renderer/render_layer.hpp>
+#include <mbgl/style/layers/heatmap_layer_impl.hpp>
+#include <mbgl/style/layers/heatmap_layer_properties.hpp>
+#include <mbgl/util/optional.hpp>
+#include <mbgl/util/offscreen_texture.hpp>
+
+namespace mbgl {
+
+class RenderHeatmapLayer: public RenderLayer {
+public:
+ RenderHeatmapLayer(Immutable<style::HeatmapLayer::Impl>);
+ ~RenderHeatmapLayer() final = default;
+
+ void transition(const TransitionParameters&) override;
+ void evaluate(const PropertyEvaluationParameters&) override;
+ bool hasTransition() const override;
+ void render(PaintParameters&, RenderSource*) override;
+
+ bool queryIntersectsFeature(
+ const GeometryCoordinates&,
+ const GeometryTileFeature&,
+ const float,
+ const float,
+ const float) const override;
+
+ void updateColorRamp();
+
+ std::unique_ptr<Bucket> createBucket(const BucketParameters&, const std::vector<const RenderLayer*>&) const override;
+
+ // Paint properties
+ style::HeatmapPaintProperties::Unevaluated unevaluated;
+ style::HeatmapPaintProperties::PossiblyEvaluated evaluated;
+
+ const style::HeatmapLayer::Impl& impl() const;
+
+ PremultipliedImage colorRamp;
+ optional<OffscreenTexture> renderTexture;
+ optional<gl::Texture> colorRampTexture;
+};
+
+template <>
+inline bool RenderLayer::is<RenderHeatmapLayer>() const {
+ return type == style::LayerType::Heatmap;
+}
+
+} // namespace mbgl
diff --git a/src/mbgl/renderer/layers/render_hillshade_layer.cpp b/src/mbgl/renderer/layers/render_hillshade_layer.cpp
index 7a767522c0..bcfd4ffe99 100644
--- a/src/mbgl/renderer/layers/render_hillshade_layer.cpp
+++ b/src/mbgl/renderer/layers/render_hillshade_layer.cpp
@@ -1,6 +1,7 @@
#include <mbgl/renderer/layers/render_hillshade_layer.hpp>
#include <mbgl/renderer/buckets/hillshade_bucket.hpp>
#include <mbgl/renderer/render_tile.hpp>
+#include <mbgl/renderer/sources/render_raster_dem_source.hpp>
#include <mbgl/renderer/paint_parameters.hpp>
#include <mbgl/renderer/render_static_data.hpp>
#include <mbgl/programs/programs.hpp>
@@ -55,10 +56,14 @@ bool RenderHillshadeLayer::hasTransition() const {
return unevaluated.hasTransition();
}
-void RenderHillshadeLayer::render(PaintParameters& parameters, RenderSource*) {
+void RenderHillshadeLayer::render(PaintParameters& parameters, RenderSource* src) {
if (parameters.pass != RenderPass::Translucent && parameters.pass != RenderPass::Pass3D)
return;
+ RenderRasterDEMSource* demsrc = dynamic_cast<RenderRasterDEMSource*>(src);
+ const uint8_t TERRAIN_RGB_MAXZOOM = 15;
+ const uint8_t maxzoom = demsrc != nullptr ? demsrc->getMaxZoom() : TERRAIN_RGB_MAXZOOM;
+
auto draw = [&] (const mat4& matrix,
const auto& vertexBuffer,
const auto& indexBuffer,
@@ -118,6 +123,7 @@ void RenderHillshadeLayer::render(PaintParameters& parameters, RenderSource*) {
uniforms::u_matrix::Value { mat },
uniforms::u_dimension::Value { {{uint16_t(tilesize * 2), uint16_t(tilesize * 2) }} },
uniforms::u_zoom::Value{ float(tile.id.canonical.z) },
+ uniforms::u_maxzoom::Value{ float(maxzoom) },
uniforms::u_image::Value{ 0 }
},
parameters.staticData.rasterVertexBuffer,
@@ -136,14 +142,14 @@ void RenderHillshadeLayer::render(PaintParameters& parameters, RenderSource*) {
if (bucket.vertexBuffer && bucket.indexBuffer && !bucket.segments.empty()) {
// Draw only the parts of the tile that aren't drawn by another tile in the layer.
- draw(tile.matrix,
+ draw(parameters.matrixForTile(tile.id, true),
*bucket.vertexBuffer,
*bucket.indexBuffer,
bucket.segments,
tile.id);
} else {
// Draw the full tile.
- draw(tile.matrix,
+ draw(parameters.matrixForTile(tile.id, true),
parameters.staticData.rasterVertexBuffer,
parameters.staticData.quadTriangleIndexBuffer,
parameters.staticData.rasterSegments,
diff --git a/src/mbgl/renderer/layers/render_hillshade_layer.hpp b/src/mbgl/renderer/layers/render_hillshade_layer.hpp
index e9b9db1ec3..13093ee7ef 100644
--- a/src/mbgl/renderer/layers/render_hillshade_layer.hpp
+++ b/src/mbgl/renderer/layers/render_hillshade_layer.hpp
@@ -16,7 +16,7 @@ public:
void evaluate(const PropertyEvaluationParameters&) override;
bool hasTransition() const override;
- void render(PaintParameters&, RenderSource*) override;
+ void render(PaintParameters&, RenderSource* src) override;
std::unique_ptr<Bucket> createBucket(const BucketParameters&, const std::vector<const RenderLayer*>&) const override;
diff --git a/src/mbgl/renderer/layers/render_symbol_layer.cpp b/src/mbgl/renderer/layers/render_symbol_layer.cpp
index 04fcb2c3ab..9e493003c0 100644
--- a/src/mbgl/renderer/layers/render_symbol_layer.cpp
+++ b/src/mbgl/renderer/layers/render_symbol_layer.cpp
@@ -268,9 +268,10 @@ void RenderSymbolLayer::render(PaintParameters& parameters, RenderSource*) {
gl::DepthMode::disabled(),
gl::StencilMode::disabled(),
parameters.colorModeForRenderPass(),
- CollisionBoxProgram::UniformValues {
+ CollisionCircleProgram::UniformValues {
uniforms::u_matrix::Value{ tile.matrix },
uniforms::u_extrude_scale::Value{ extrudeScale },
+ uniforms::u_overscale_factor::Value{ float(tile.tile.id.overscaleFactor()) },
uniforms::u_camera_to_center_distance::Value{ parameters.state.getCameraToCenterDistance() }
},
*bucket.collisionCircle.vertexBuffer,
diff --git a/src/mbgl/renderer/render_layer.cpp b/src/mbgl/renderer/render_layer.cpp
index 248905f834..bcdc175f14 100644
--- a/src/mbgl/renderer/render_layer.cpp
+++ b/src/mbgl/renderer/render_layer.cpp
@@ -8,6 +8,7 @@
#include <mbgl/renderer/layers/render_line_layer.hpp>
#include <mbgl/renderer/layers/render_raster_layer.hpp>
#include <mbgl/renderer/layers/render_symbol_layer.hpp>
+#include <mbgl/renderer/layers/render_heatmap_layer.hpp>
#include <mbgl/style/types.hpp>
#include <mbgl/renderer/render_tile.hpp>
@@ -35,6 +36,8 @@ std::unique_ptr<RenderLayer> RenderLayer::create(Immutable<Layer::Impl> impl) {
return std::make_unique<RenderCustomLayer>(staticImmutableCast<CustomLayer::Impl>(impl));
case LayerType::FillExtrusion:
return std::make_unique<RenderFillExtrusionLayer>(staticImmutableCast<FillExtrusionLayer::Impl>(impl));
+ case LayerType::Heatmap:
+ return std::make_unique<RenderHeatmapLayer>(staticImmutableCast<HeatmapLayer::Impl>(impl));
}
// Not reachable, but placate GCC.
diff --git a/src/mbgl/renderer/render_source.hpp b/src/mbgl/renderer/render_source.hpp
index db88230e53..53519c763e 100644
--- a/src/mbgl/renderer/render_source.hpp
+++ b/src/mbgl/renderer/render_source.hpp
@@ -70,7 +70,7 @@ public:
virtual std::vector<Feature>
querySourceFeatures(const SourceQueryOptions&) const = 0;
- virtual void onLowMemory() = 0;
+ virtual void reduceMemoryUse() = 0;
virtual void dumpDebugLogs() const = 0;
diff --git a/src/mbgl/renderer/renderer.cpp b/src/mbgl/renderer/renderer.cpp
index 6d086c70b1..1d2f2bb522 100644
--- a/src/mbgl/renderer/renderer.cpp
+++ b/src/mbgl/renderer/renderer.cpp
@@ -94,9 +94,9 @@ void Renderer::dumpDebugLogs() {
impl->dumDebugLogs();
}
-void Renderer::onLowMemory() {
+void Renderer::reduceMemoryUse() {
BackendScope guard { impl->backend };
- impl->onLowMemory();
+ impl->reduceMemoryUse();
}
} // namespace mbgl
diff --git a/src/mbgl/renderer/renderer_impl.cpp b/src/mbgl/renderer/renderer_impl.cpp
index 61e7d17242..2ac714e122 100644
--- a/src/mbgl/renderer/renderer_impl.cpp
+++ b/src/mbgl/renderer/renderer_impl.cpp
@@ -14,6 +14,7 @@
#include <mbgl/renderer/layers/render_background_layer.hpp>
#include <mbgl/renderer/layers/render_custom_layer.hpp>
#include <mbgl/renderer/layers/render_fill_extrusion_layer.hpp>
+#include <mbgl/renderer/layers/render_heatmap_layer.hpp>
#include <mbgl/renderer/layers/render_hillshade_layer.hpp>
#include <mbgl/renderer/style_diff.hpp>
#include <mbgl/renderer/query.hpp>
@@ -185,6 +186,10 @@ void Renderer::Impl::render(const UpdateParameters& updateParameters) {
if (layerAdded || layerChanged) {
layer.transition(transitionParameters);
+
+ if (layer.is<RenderHeatmapLayer>()) {
+ layer.as<RenderHeatmapLayer>()->updateColorRamp();
+ }
}
if (layerAdded || layerChanged || zoomChanged || layer.hasTransition()) {
@@ -290,7 +295,11 @@ void Renderer::Impl::render(const UpdateParameters& updateParameters) {
RenderLayer* layer = getRenderLayer(layerImpl->id);
assert(layer);
- if (!parameters.staticData.has3D && (layer->is<RenderFillExtrusionLayer>() || layer->is<RenderHillshadeLayer>())) {
+ if (!parameters.staticData.has3D && (
+ layer->is<RenderFillExtrusionLayer>() ||
+ layer->is<RenderHillshadeLayer>() ||
+ layer->is<RenderHeatmapLayer>())) {
+
parameters.staticData.has3D = true;
}
@@ -722,12 +731,12 @@ std::vector<Feature> Renderer::Impl::querySourceFeatures(const std::string& sour
return source->querySourceFeatures(options);
}
-void Renderer::Impl::onLowMemory() {
+void Renderer::Impl::reduceMemoryUse() {
assert(BackendScope::exists());
- backend.getContext().performCleanup();
for (const auto& entry : renderSources) {
- entry.second->onLowMemory();
+ entry.second->reduceMemoryUse();
}
+ backend.getContext().performCleanup();
observer->onInvalidate();
}
diff --git a/src/mbgl/renderer/renderer_impl.hpp b/src/mbgl/renderer/renderer_impl.hpp
index 4f45d514a5..4675ac79ea 100644
--- a/src/mbgl/renderer/renderer_impl.hpp
+++ b/src/mbgl/renderer/renderer_impl.hpp
@@ -53,7 +53,7 @@ public:
std::vector<Feature> querySourceFeatures(const std::string& sourceID, const SourceQueryOptions&) const;
std::vector<Feature> queryShapeAnnotations(const ScreenLineString&) const;
- void onLowMemory();
+ void reduceMemoryUse();
void dumDebugLogs();
private:
diff --git a/src/mbgl/renderer/sources/render_custom_geometry_source.cpp b/src/mbgl/renderer/sources/render_custom_geometry_source.cpp
index df615a7e20..057ad5a4a7 100644
--- a/src/mbgl/renderer/sources/render_custom_geometry_source.cpp
+++ b/src/mbgl/renderer/sources/render_custom_geometry_source.cpp
@@ -76,8 +76,8 @@ std::vector<Feature> RenderCustomGeometrySource::querySourceFeatures(const Sourc
return tilePyramid.querySourceFeatures(options);
}
-void RenderCustomGeometrySource::onLowMemory() {
- tilePyramid.onLowMemory();
+void RenderCustomGeometrySource::reduceMemoryUse() {
+ tilePyramid.reduceMemoryUse();
}
void RenderCustomGeometrySource::dumpDebugLogs() const {
diff --git a/src/mbgl/renderer/sources/render_custom_geometry_source.hpp b/src/mbgl/renderer/sources/render_custom_geometry_source.hpp
index 82e691d5c9..033d731029 100644
--- a/src/mbgl/renderer/sources/render_custom_geometry_source.hpp
+++ b/src/mbgl/renderer/sources/render_custom_geometry_source.hpp
@@ -33,7 +33,7 @@ public:
std::vector<Feature>
querySourceFeatures(const SourceQueryOptions&) const final;
- void onLowMemory() final;
+ void reduceMemoryUse() final;
void dumpDebugLogs() const final;
private:
diff --git a/src/mbgl/renderer/sources/render_geojson_source.cpp b/src/mbgl/renderer/sources/render_geojson_source.cpp
index 8ea80cd813..cbf4db70b5 100644
--- a/src/mbgl/renderer/sources/render_geojson_source.cpp
+++ b/src/mbgl/renderer/sources/render_geojson_source.cpp
@@ -94,8 +94,8 @@ std::vector<Feature> RenderGeoJSONSource::querySourceFeatures(const SourceQueryO
return tilePyramid.querySourceFeatures(options);
}
-void RenderGeoJSONSource::onLowMemory() {
- tilePyramid.onLowMemory();
+void RenderGeoJSONSource::reduceMemoryUse() {
+ tilePyramid.reduceMemoryUse();
}
void RenderGeoJSONSource::dumpDebugLogs() const {
diff --git a/src/mbgl/renderer/sources/render_geojson_source.hpp b/src/mbgl/renderer/sources/render_geojson_source.hpp
index 55166ea901..72fccbd043 100644
--- a/src/mbgl/renderer/sources/render_geojson_source.hpp
+++ b/src/mbgl/renderer/sources/render_geojson_source.hpp
@@ -37,7 +37,7 @@ public:
std::vector<Feature>
querySourceFeatures(const SourceQueryOptions&) const final;
- void onLowMemory() final;
+ void reduceMemoryUse() final;
void dumpDebugLogs() const final;
private:
diff --git a/src/mbgl/renderer/sources/render_image_source.hpp b/src/mbgl/renderer/sources/render_image_source.hpp
index 72cf4cea61..85ee0ace11 100644
--- a/src/mbgl/renderer/sources/render_image_source.hpp
+++ b/src/mbgl/renderer/sources/render_image_source.hpp
@@ -37,7 +37,7 @@ public:
std::vector<Feature> querySourceFeatures(const SourceQueryOptions&) const final;
- void onLowMemory() final {
+ void reduceMemoryUse() final {
}
void dumpDebugLogs() const final;
diff --git a/src/mbgl/renderer/sources/render_raster_dem_source.cpp b/src/mbgl/renderer/sources/render_raster_dem_source.cpp
index 76716518d7..b3153622c3 100644
--- a/src/mbgl/renderer/sources/render_raster_dem_source.cpp
+++ b/src/mbgl/renderer/sources/render_raster_dem_source.cpp
@@ -32,21 +32,22 @@ void RenderRasterDEMSource::update(Immutable<style::Source::Impl> baseImpl_,
enabled = needsRendering;
- optional<Tileset> tileset = impl().getTileset();
-
- if (!tileset) {
- return;
- }
-
- if (tileURLTemplates != tileset->tiles) {
- tileURLTemplates = tileset->tiles;
+ optional<Tileset> _tileset = impl().getTileset();
+ if (tileset != _tileset) {
+ tileset = _tileset;
+ maxzoom = tileset->zoomRange.max;
// TODO: this removes existing buckets, and will cause flickering.
// Should instead refresh tile data in place.
tilePyramid.tiles.clear();
tilePyramid.renderTiles.clear();
tilePyramid.cache.clear();
}
+ // Allow clearing the tile pyramid first, before the early return in case
+ // the new tileset is not yet available or has an error in loading
+ if (!_tileset) {
+ return;
+ }
tilePyramid.update(layers,
needsRendering,
@@ -154,8 +155,8 @@ std::vector<Feature> RenderRasterDEMSource::querySourceFeatures(const SourceQuer
return {};
}
-void RenderRasterDEMSource::onLowMemory() {
- tilePyramid.onLowMemory();
+void RenderRasterDEMSource::reduceMemoryUse() {
+ tilePyramid.reduceMemoryUse();
}
void RenderRasterDEMSource::dumpDebugLogs() const {
diff --git a/src/mbgl/renderer/sources/render_raster_dem_source.hpp b/src/mbgl/renderer/sources/render_raster_dem_source.hpp
index b6b8bf977c..741214a14d 100644
--- a/src/mbgl/renderer/sources/render_raster_dem_source.hpp
+++ b/src/mbgl/renderer/sources/render_raster_dem_source.hpp
@@ -33,14 +33,19 @@ public:
std::vector<Feature>
querySourceFeatures(const SourceQueryOptions&) const final;
- void onLowMemory() final;
+ void reduceMemoryUse() final;
void dumpDebugLogs() const final;
+ uint8_t getMaxZoom() const {
+ return maxzoom;
+ };
+
private:
const style::RasterSource::Impl& impl() const;
TilePyramid tilePyramid;
- optional<std::vector<std::string>> tileURLTemplates;
+ optional<Tileset> tileset;
+ uint8_t maxzoom = 15;
protected:
void onTileChanged(Tile&) final;
diff --git a/src/mbgl/renderer/sources/render_raster_source.cpp b/src/mbgl/renderer/sources/render_raster_source.cpp
index e99cd040e9..60b3fa9a3b 100644
--- a/src/mbgl/renderer/sources/render_raster_source.cpp
+++ b/src/mbgl/renderer/sources/render_raster_source.cpp
@@ -29,14 +29,10 @@ void RenderRasterSource::update(Immutable<style::Source::Impl> baseImpl_,
enabled = needsRendering;
- optional<Tileset> tileset = impl().getTileset();
+ optional<Tileset> _tileset = impl().getTileset();
- if (!tileset) {
- return;
- }
-
- if (tileURLTemplates != tileset->tiles) {
- tileURLTemplates = tileset->tiles;
+ if (tileset != _tileset) {
+ tileset = _tileset;
// TODO: this removes existing buckets, and will cause flickering.
// Should instead refresh tile data in place.
@@ -44,6 +40,11 @@ void RenderRasterSource::update(Immutable<style::Source::Impl> baseImpl_,
tilePyramid.renderTiles.clear();
tilePyramid.cache.clear();
}
+ // Allow clearing the tile pyramid first, before the early return in case
+ // the new tileset is not yet available or has an error in loading
+ if (!_tileset) {
+ return;
+ }
tilePyramid.update(layers,
needsRendering,
@@ -84,8 +85,8 @@ std::vector<Feature> RenderRasterSource::querySourceFeatures(const SourceQueryOp
return {};
}
-void RenderRasterSource::onLowMemory() {
- tilePyramid.onLowMemory();
+void RenderRasterSource::reduceMemoryUse() {
+ tilePyramid.reduceMemoryUse();
}
void RenderRasterSource::dumpDebugLogs() const {
diff --git a/src/mbgl/renderer/sources/render_raster_source.hpp b/src/mbgl/renderer/sources/render_raster_source.hpp
index 25041fde43..78eda199ac 100644
--- a/src/mbgl/renderer/sources/render_raster_source.hpp
+++ b/src/mbgl/renderer/sources/render_raster_source.hpp
@@ -33,14 +33,14 @@ public:
std::vector<Feature>
querySourceFeatures(const SourceQueryOptions&) const final;
- void onLowMemory() final;
+ void reduceMemoryUse() final;
void dumpDebugLogs() const final;
private:
const style::RasterSource::Impl& impl() const;
TilePyramid tilePyramid;
- optional<std::vector<std::string>> tileURLTemplates;
+ optional<Tileset> tileset;
};
template <>
diff --git a/src/mbgl/renderer/sources/render_vector_source.cpp b/src/mbgl/renderer/sources/render_vector_source.cpp
index d53023e4d0..e87bea5dcd 100644
--- a/src/mbgl/renderer/sources/render_vector_source.cpp
+++ b/src/mbgl/renderer/sources/render_vector_source.cpp
@@ -32,14 +32,10 @@ void RenderVectorSource::update(Immutable<style::Source::Impl> baseImpl_,
enabled = needsRendering;
- optional<Tileset> tileset = impl().getTileset();
+ optional<Tileset> _tileset = impl().getTileset();
- if (!tileset) {
- return;
- }
-
- if (tileURLTemplates != tileset->tiles) {
- tileURLTemplates = tileset->tiles;
+ if (tileset != _tileset) {
+ tileset = _tileset;
// TODO: this removes existing buckets, and will cause flickering.
// Should instead refresh tile data in place.
@@ -47,6 +43,11 @@ void RenderVectorSource::update(Immutable<style::Source::Impl> baseImpl_,
tilePyramid.renderTiles.clear();
tilePyramid.cache.clear();
}
+ // Allow clearing the tile pyramid first, before the early return in case
+ // the new tileset is not yet available or has an error in loading
+ if (!_tileset) {
+ return;
+ }
tilePyramid.update(layers,
needsRendering,
@@ -87,8 +88,8 @@ std::vector<Feature> RenderVectorSource::querySourceFeatures(const SourceQueryOp
return tilePyramid.querySourceFeatures(options);
}
-void RenderVectorSource::onLowMemory() {
- tilePyramid.onLowMemory();
+void RenderVectorSource::reduceMemoryUse() {
+ tilePyramid.reduceMemoryUse();
}
void RenderVectorSource::dumpDebugLogs() const {
diff --git a/src/mbgl/renderer/sources/render_vector_source.hpp b/src/mbgl/renderer/sources/render_vector_source.hpp
index 4a992e854f..592160dc16 100644
--- a/src/mbgl/renderer/sources/render_vector_source.hpp
+++ b/src/mbgl/renderer/sources/render_vector_source.hpp
@@ -33,14 +33,14 @@ public:
std::vector<Feature>
querySourceFeatures(const SourceQueryOptions&) const final;
- void onLowMemory() final;
+ void reduceMemoryUse() final;
void dumpDebugLogs() const final;
private:
const style::VectorSource::Impl& impl() const;
TilePyramid tilePyramid;
- optional<std::vector<std::string>> tileURLTemplates;
+ optional<Tileset> tileset;
};
template <>
diff --git a/src/mbgl/renderer/tile_pyramid.cpp b/src/mbgl/renderer/tile_pyramid.cpp
index 07239b7a1c..8f83a0f982 100644
--- a/src/mbgl/renderer/tile_pyramid.cpp
+++ b/src/mbgl/renderer/tile_pyramid.cpp
@@ -143,10 +143,13 @@ void TilePyramid::update(const std::vector<Immutable<style::Layer::Impl>>& layer
auto it = tiles.find(tileID);
return it == tiles.end() ? nullptr : it->second.get();
};
-
+
+ // The min and max zoom for TileRange are based on the updateRenderables algorithm.
+ // Tiles are created at the ideal tile zoom or at lower zoom levels. Child
+ // tiles are used from the cache, but not created.
optional<util::TileRange> tileRange = {};
if (bounds) {
- tileRange = util::TileRange::fromLatLngBounds(*bounds, std::min(tileZoom, (int32_t)zoomRange.max));
+ tileRange = util::TileRange::fromLatLngBounds(*bounds, zoomRange.min, std::min(tileZoom, (int32_t)zoomRange.max));
}
auto createTileFn = [&](const OverscaledTileID& tileID) -> Tile* {
if (tileRange && !tileRange->contains(tileID.canonical)) {
@@ -303,7 +306,7 @@ void TilePyramid::setCacheSize(size_t size) {
cache.setSize(size);
}
-void TilePyramid::onLowMemory() {
+void TilePyramid::reduceMemoryUse() {
cache.clear();
}
diff --git a/src/mbgl/renderer/tile_pyramid.hpp b/src/mbgl/renderer/tile_pyramid.hpp
index ad3f91bf88..2638599c38 100644
--- a/src/mbgl/renderer/tile_pyramid.hpp
+++ b/src/mbgl/renderer/tile_pyramid.hpp
@@ -59,7 +59,7 @@ public:
std::vector<Feature> querySourceFeatures(const SourceQueryOptions&) const;
void setCacheSize(size_t);
- void onLowMemory();
+ void reduceMemoryUse();
void setObserver(TileObserver*);
void dumpDebugLogs() const;
diff --git a/src/mbgl/shaders/collision_circle.cpp b/src/mbgl/shaders/collision_circle.cpp
index f220586245..82ebbf05a0 100644
--- a/src/mbgl/shaders/collision_circle.cpp
+++ b/src/mbgl/shaders/collision_circle.cpp
@@ -26,7 +26,10 @@ varying vec2 v_extrude_scale;
void main() {
vec4 projectedPoint = u_matrix * vec4(a_anchor_pos, 0, 1);
highp float camera_to_anchor_distance = projectedPoint.w;
- highp float collision_perspective_ratio = 0.5 + 0.5 * (u_camera_to_center_distance / camera_to_anchor_distance);
+ highp float collision_perspective_ratio = clamp(
+ 0.5 + 0.5 * (u_camera_to_center_distance / camera_to_anchor_distance),
+ 0.0, // Prevents oversized near-field circles in pitched/overzoomed tiles
+ 4.0);
gl_Position = u_matrix * vec4(a_pos, 0.0, 1.0);
@@ -43,6 +46,7 @@ void main() {
)MBGL_SHADER";
const char* collision_circle::fragmentSource = R"MBGL_SHADER(
+uniform float u_overscale_factor;
varying float v_placed;
varying float v_notUsed;
@@ -68,7 +72,7 @@ void main() {
float extrude_scale_length = length(v_extrude_scale);
float extrude_length = length(v_extrude) * extrude_scale_length;
- float stroke_width = 15.0 * extrude_scale_length;
+ float stroke_width = 15.0 * extrude_scale_length / u_overscale_factor;
float radius = v_radius * extrude_scale_length;
float distance_to_edge = abs(extrude_length - radius);
diff --git a/src/mbgl/shaders/heatmap.cpp b/src/mbgl/shaders/heatmap.cpp
new file mode 100644
index 0000000000..19927cfcc6
--- /dev/null
+++ b/src/mbgl/shaders/heatmap.cpp
@@ -0,0 +1,128 @@
+// NOTE: DO NOT CHANGE THIS FILE. IT IS AUTOMATICALLY GENERATED.
+
+#include <mbgl/shaders/heatmap.hpp>
+
+namespace mbgl {
+namespace shaders {
+
+const char* heatmap::name = "heatmap";
+const char* heatmap::vertexSource = R"MBGL_SHADER(
+
+#ifndef HAS_UNIFORM_u_weight
+uniform lowp float a_weight_t;
+attribute highp vec2 a_weight;
+varying highp float weight;
+#else
+uniform highp float u_weight;
+#endif
+
+
+#ifndef HAS_UNIFORM_u_radius
+uniform lowp float a_radius_t;
+attribute mediump vec2 a_radius;
+#else
+uniform mediump float u_radius;
+#endif
+
+
+uniform mat4 u_matrix;
+uniform float u_extrude_scale;
+uniform float u_opacity;
+uniform float u_intensity;
+
+attribute vec2 a_pos;
+
+varying vec2 v_extrude;
+
+// Effective "0" in the kernel density texture to adjust the kernel size to;
+// this empirically chosen number minimizes artifacts on overlapping kernels
+// for typical heatmap cases (assuming clustered source)
+const highp float ZERO = 1.0 / 255.0 / 16.0;
+
+// Gaussian kernel coefficient: 1 / sqrt(2 * PI)
+#define GAUSS_COEF 0.3989422804014327
+
+void main(void) {
+
+#ifndef HAS_UNIFORM_u_weight
+ weight = unpack_mix_vec2(a_weight, a_weight_t);
+#else
+ highp float weight = u_weight;
+#endif
+
+
+#ifndef HAS_UNIFORM_u_radius
+ mediump float radius = unpack_mix_vec2(a_radius, a_radius_t);
+#else
+ mediump float radius = u_radius;
+#endif
+
+
+ // unencode the extrusion vector that we snuck into the a_pos vector
+ vec2 unscaled_extrude = vec2(mod(a_pos, 2.0) * 2.0 - 1.0);
+
+ // This 'extrude' comes in ranging from [-1, -1], to [1, 1]. We'll use
+ // it to produce the vertices of a square mesh framing the point feature
+ // we're adding to the kernel density texture. We'll also pass it as
+ // a varying, so that the fragment shader can determine the distance of
+ // each fragment from the point feature.
+ // Before we do so, we need to scale it up sufficiently so that the
+ // kernel falls effectively to zero at the edge of the mesh.
+ // That is, we want to know S such that
+ // weight * u_intensity * GAUSS_COEF * exp(-0.5 * 3.0^2 * S^2) == ZERO
+ // Which solves to:
+ // S = sqrt(-2.0 * log(ZERO / (weight * u_intensity * GAUSS_COEF))) / 3.0
+ float S = sqrt(-2.0 * log(ZERO / weight / u_intensity / GAUSS_COEF)) / 3.0;
+
+ // Pass the varying in units of radius
+ v_extrude = S * unscaled_extrude;
+
+ // Scale by radius and the zoom-based scale factor to produce actual
+ // mesh position
+ vec2 extrude = v_extrude * radius * u_extrude_scale;
+
+ // multiply a_pos by 0.5, since we had it * 2 in order to sneak
+ // in extrusion data
+ vec4 pos = vec4(floor(a_pos * 0.5) + extrude, 0, 1);
+
+ gl_Position = u_matrix * pos;
+}
+
+)MBGL_SHADER";
+const char* heatmap::fragmentSource = R"MBGL_SHADER(
+
+#ifndef HAS_UNIFORM_u_weight
+varying highp float weight;
+#else
+uniform highp float u_weight;
+#endif
+
+
+uniform highp float u_intensity;
+varying vec2 v_extrude;
+
+// Gaussian kernel coefficient: 1 / sqrt(2 * PI)
+#define GAUSS_COEF 0.3989422804014327
+
+void main() {
+
+#ifdef HAS_UNIFORM_u_weight
+ highp float weight = u_weight;
+#endif
+
+
+ // Kernel density estimation with a Gaussian kernel of size 5x5
+ float d = -0.5 * 3.0 * 3.0 * dot(v_extrude, v_extrude);
+ float val = weight * u_intensity * GAUSS_COEF * exp(d);
+
+ gl_FragColor = vec4(val, 1.0, 1.0, 1.0);
+
+#ifdef OVERDRAW_INSPECTOR
+ gl_FragColor = vec4(1.0);
+#endif
+}
+
+)MBGL_SHADER";
+
+} // namespace shaders
+} // namespace mbgl
diff --git a/src/mbgl/shaders/heatmap.hpp b/src/mbgl/shaders/heatmap.hpp
new file mode 100644
index 0000000000..a3c64db942
--- /dev/null
+++ b/src/mbgl/shaders/heatmap.hpp
@@ -0,0 +1,16 @@
+// NOTE: DO NOT CHANGE THIS FILE. IT IS AUTOMATICALLY GENERATED.
+
+#pragma once
+
+namespace mbgl {
+namespace shaders {
+
+class heatmap {
+public:
+ static const char* name;
+ static const char* vertexSource;
+ static const char* fragmentSource;
+};
+
+} // namespace shaders
+} // namespace mbgl
diff --git a/src/mbgl/shaders/heatmap_texture.cpp b/src/mbgl/shaders/heatmap_texture.cpp
new file mode 100644
index 0000000000..c5d35c48ae
--- /dev/null
+++ b/src/mbgl/shaders/heatmap_texture.cpp
@@ -0,0 +1,42 @@
+// NOTE: DO NOT CHANGE THIS FILE. IT IS AUTOMATICALLY GENERATED.
+
+#include <mbgl/shaders/heatmap_texture.hpp>
+
+namespace mbgl {
+namespace shaders {
+
+const char* heatmap_texture::name = "heatmap_texture";
+const char* heatmap_texture::vertexSource = R"MBGL_SHADER(
+uniform mat4 u_matrix;
+uniform vec2 u_world;
+attribute vec2 a_pos;
+varying vec2 v_pos;
+
+void main() {
+ gl_Position = u_matrix * vec4(a_pos * u_world, 0, 1);
+
+ v_pos.x = a_pos.x;
+ v_pos.y = 1.0 - a_pos.y;
+}
+
+)MBGL_SHADER";
+const char* heatmap_texture::fragmentSource = R"MBGL_SHADER(
+uniform sampler2D u_image;
+uniform sampler2D u_color_ramp;
+uniform float u_opacity;
+varying vec2 v_pos;
+
+void main() {
+ float t = texture2D(u_image, v_pos).r;
+ vec4 color = texture2D(u_color_ramp, vec2(t, 0.5));
+ gl_FragColor = color * u_opacity;
+
+#ifdef OVERDRAW_INSPECTOR
+ gl_FragColor = vec4(0.0);
+#endif
+}
+
+)MBGL_SHADER";
+
+} // namespace shaders
+} // namespace mbgl
diff --git a/src/mbgl/shaders/heatmap_texture.hpp b/src/mbgl/shaders/heatmap_texture.hpp
new file mode 100644
index 0000000000..c51dc6b178
--- /dev/null
+++ b/src/mbgl/shaders/heatmap_texture.hpp
@@ -0,0 +1,16 @@
+// NOTE: DO NOT CHANGE THIS FILE. IT IS AUTOMATICALLY GENERATED.
+
+#pragma once
+
+namespace mbgl {
+namespace shaders {
+
+class heatmap_texture {
+public:
+ static const char* name;
+ static const char* vertexSource;
+ static const char* fragmentSource;
+};
+
+} // namespace shaders
+} // namespace mbgl
diff --git a/src/mbgl/shaders/hillshade_prepare.cpp b/src/mbgl/shaders/hillshade_prepare.cpp
index 733658435e..8d0571f6a4 100644
--- a/src/mbgl/shaders/hillshade_prepare.cpp
+++ b/src/mbgl/shaders/hillshade_prepare.cpp
@@ -29,6 +29,7 @@ uniform sampler2D u_image;
varying vec2 v_pos;
uniform vec2 u_dimension;
uniform float u_zoom;
+uniform float u_maxzoom;
float getElevation(vec2 coord, float bias) {
// Convert encoded elevation value to meters
@@ -71,16 +72,16 @@ void main() {
// which can be reduced to: pow(2, 19.25619978527 - u_zoom)
// we want to vertically exaggerate the hillshading though, because otherwise
// it is barely noticeable at low zooms. to do this, we multiply this by some
- // scale factor pow(2, (u_zoom - 14) * a) where a is an arbitrary value and 14 is the
- // maxzoom of the tile source. here we use a=0.3 which works out to the
- // expression below. see nickidlugash's awesome breakdown for more info
+ // scale factor pow(2, (u_zoom - u_maxzoom) * a) where a is an arbitrary value
+ // Here we use a=0.3 which works out to the expression below. see
+ // nickidlugash's awesome breakdown for more info
// https://github.com/mapbox/mapbox-gl-js/pull/5286#discussion_r148419556
float exaggeration = u_zoom < 2.0 ? 0.4 : u_zoom < 4.5 ? 0.35 : 0.3;
vec2 deriv = vec2(
(c + f + f + i) - (a + d + d + g),
(g + h + h + i) - (a + b + b + c)
- ) / pow(2.0, (u_zoom - 14.0) * exaggeration + 19.2562 - u_zoom);
+ ) / pow(2.0, (u_zoom - u_maxzoom) * exaggeration + 19.2562 - u_zoom);
gl_FragColor = clamp(vec4(
deriv.x / 2.0 + 0.5,
diff --git a/src/mbgl/style/conversion/layer.cpp b/src/mbgl/style/conversion/layer.cpp
index ad6998341d..19472bc8d6 100644
--- a/src/mbgl/style/conversion/layer.cpp
+++ b/src/mbgl/style/conversion/layer.cpp
@@ -6,6 +6,7 @@
#include <mbgl/style/layers/circle_layer.hpp>
#include <mbgl/style/layers/fill_layer.hpp>
#include <mbgl/style/layers/fill_extrusion_layer.hpp>
+#include <mbgl/style/layers/heatmap_layer.hpp>
#include <mbgl/style/layers/hillshade_layer.hpp>
#include <mbgl/style/layers/line_layer.hpp>
#include <mbgl/style/layers/raster_layer.hpp>
@@ -165,6 +166,8 @@ optional<std::unique_ptr<Layer>> Converter<std::unique_ptr<Layer>>::operator()(c
converted = convertVectorLayer<SymbolLayer>(*id, value, error);
} else if (*type == "raster") {
converted = convertRasterLayer(*id, value, error);
+ } else if (*type == "heatmap") {
+ converted = convertVectorLayer<HeatmapLayer>(*id, value, error);
} else if (*type == "hillshade") {
converted = convertHillshadeLayer(*id, value, error);
} else if (*type == "background") {
diff --git a/src/mbgl/style/conversion/make_property_setters.hpp b/src/mbgl/style/conversion/make_property_setters.hpp
index adfcc4dd61..25c8fdb1ca 100644
--- a/src/mbgl/style/conversion/make_property_setters.hpp
+++ b/src/mbgl/style/conversion/make_property_setters.hpp
@@ -8,6 +8,7 @@
#include <mbgl/style/layers/line_layer.hpp>
#include <mbgl/style/layers/symbol_layer.hpp>
#include <mbgl/style/layers/circle_layer.hpp>
+#include <mbgl/style/layers/heatmap_layer.hpp>
#include <mbgl/style/layers/fill_extrusion_layer.hpp>
#include <mbgl/style/layers/raster_layer.hpp>
#include <mbgl/style/layers/hillshade_layer.hpp>
@@ -72,6 +73,7 @@ inline auto makeLayoutPropertySetters() {
+
return result;
}
@@ -166,6 +168,17 @@ inline auto makePaintPropertySetters() {
result["circle-stroke-opacity"] = &setProperty<CircleLayer, DataDrivenPropertyValue<float>, &CircleLayer::setCircleStrokeOpacity>;
result["circle-stroke-opacity-transition"] = &setTransition<CircleLayer, &CircleLayer::setCircleStrokeOpacityTransition>;
+ result["heatmap-radius"] = &setProperty<HeatmapLayer, DataDrivenPropertyValue<float>, &HeatmapLayer::setHeatmapRadius>;
+ result["heatmap-radius-transition"] = &setTransition<HeatmapLayer, &HeatmapLayer::setHeatmapRadiusTransition>;
+ result["heatmap-weight"] = &setProperty<HeatmapLayer, DataDrivenPropertyValue<float>, &HeatmapLayer::setHeatmapWeight>;
+ result["heatmap-weight-transition"] = &setTransition<HeatmapLayer, &HeatmapLayer::setHeatmapWeightTransition>;
+ result["heatmap-intensity"] = &setProperty<HeatmapLayer, PropertyValue<float>, &HeatmapLayer::setHeatmapIntensity>;
+ result["heatmap-intensity-transition"] = &setTransition<HeatmapLayer, &HeatmapLayer::setHeatmapIntensityTransition>;
+ result["heatmap-color"] = &setProperty<HeatmapLayer, HeatmapColorPropertyValue, &HeatmapLayer::setHeatmapColor>;
+ result["heatmap-color-transition"] = &setTransition<HeatmapLayer, &HeatmapLayer::setHeatmapColorTransition>;
+ result["heatmap-opacity"] = &setProperty<HeatmapLayer, PropertyValue<float>, &HeatmapLayer::setHeatmapOpacity>;
+ result["heatmap-opacity-transition"] = &setTransition<HeatmapLayer, &HeatmapLayer::setHeatmapOpacityTransition>;
+
result["fill-extrusion-opacity"] = &setProperty<FillExtrusionLayer, PropertyValue<float>, &FillExtrusionLayer::setFillExtrusionOpacity>;
result["fill-extrusion-opacity-transition"] = &setTransition<FillExtrusionLayer, &FillExtrusionLayer::setFillExtrusionOpacityTransition>;
result["fill-extrusion-color"] = &setProperty<FillExtrusionLayer, DataDrivenPropertyValue<Color>, &FillExtrusionLayer::setFillExtrusionColor>;
diff --git a/src/mbgl/style/conversion/property_setter.hpp b/src/mbgl/style/conversion/property_setter.hpp
index 9e382b9c38..e3716a18dc 100644
--- a/src/mbgl/style/conversion/property_setter.hpp
+++ b/src/mbgl/style/conversion/property_setter.hpp
@@ -5,6 +5,7 @@
#include <mbgl/style/conversion/constant.hpp>
#include <mbgl/style/conversion/property_value.hpp>
#include <mbgl/style/conversion/data_driven_property_value.hpp>
+#include <mbgl/style/conversion/heatmap_color_property_value.hpp>
#include <mbgl/style/conversion/transition_options.hpp>
#include <string>
diff --git a/src/mbgl/style/conversion/tileset.cpp b/src/mbgl/style/conversion/tileset.cpp
index 6e559c0cac..6d89cef944 100644
--- a/src/mbgl/style/conversion/tileset.cpp
+++ b/src/mbgl/style/conversion/tileset.cpp
@@ -6,7 +6,7 @@ namespace style {
namespace conversion {
bool validateLatitude(const double lat) {
- return lat < 90 && lat > -90;
+ return lat <= 90 && lat >= -90;
}
optional<Tileset> Converter<Tileset>::operator()(const Convertible& value, Error& error) const {
@@ -40,6 +40,16 @@ optional<Tileset> Converter<Tileset>::operator()(const Convertible& value, Error
}
}
+ auto encodingValue = objectMember(value, "encoding");
+ if (encodingValue) {
+ optional<std::string> encoding = toString(*encodingValue);
+ if (encoding && *encoding == "terrarium") {
+ result.encoding = Tileset::DEMEncoding::Terrarium;
+ } else if (encoding && *encoding != "mapbox") {
+ error = { "invalid raster-dem encoding type - valid types are 'mapbox' and 'terrarium' " };
+ }
+ }
+
auto minzoomValue = objectMember(value, "minzoom");
if (minzoomValue) {
optional<float> minzoom = toNumber(*minzoomValue);
diff --git a/src/mbgl/style/expression/array_assertion.cpp b/src/mbgl/style/expression/array_assertion.cpp
index 29f6a47b10..4049301b0b 100644
--- a/src/mbgl/style/expression/array_assertion.cpp
+++ b/src/mbgl/style/expression/array_assertion.cpp
@@ -81,6 +81,25 @@ ParseResult ArrayAssertion::parse(const Convertible& value, ParsingContext& ctx)
));
}
+mbgl::Value ArrayAssertion::serialize() const {
+ std::vector<mbgl::Value> serialized;
+ serialized.emplace_back(getOperator());
+
+
+ const auto array = getType().get<type::Array>();
+ if (array.itemType.is<type::StringType>()
+ || array.itemType.is<type::NumberType>()
+ || array.itemType.is<type::BooleanType>()) {
+ serialized.emplace_back(type::toString(array.itemType));
+ if (array.N) {
+ serialized.emplace_back(uint64_t(*array.N));
+ }
+ }
+
+ serialized.emplace_back(input->serialize());
+ return serialized;
+}
+
} // namespace expression
} // namespace style
} // namespace mbgl
diff --git a/src/mbgl/style/expression/assertion.cpp b/src/mbgl/style/expression/assertion.cpp
index 0187921af9..d6f3f1b584 100644
--- a/src/mbgl/style/expression/assertion.cpp
+++ b/src/mbgl/style/expression/assertion.cpp
@@ -35,6 +35,10 @@ ParseResult Assertion::parse(const Convertible& value, ParsingContext& ctx) {
return ParseResult(std::make_unique<Assertion>(it->second, std::move(parsed)));
}
+std::string Assertion::getOperator() const {
+ return type::toString(getType());
+}
+
EvaluationResult Assertion::evaluate(const EvaluationContext& params) const {
for (std::size_t i = 0; i < inputs.size(); i++) {
EvaluationResult value = inputs[i]->evaluate(params);
diff --git a/src/mbgl/style/expression/coercion.cpp b/src/mbgl/style/expression/coercion.cpp
index 56ab33fcfd..d9cd3ffdc9 100644
--- a/src/mbgl/style/expression/coercion.cpp
+++ b/src/mbgl/style/expression/coercion.cpp
@@ -81,6 +81,13 @@ Coercion::Coercion(type::Type type_, std::vector<std::unique_ptr<Expression>> in
}
}
+std::string Coercion::getOperator() const {
+ return getType().match(
+ [](const type::NumberType&) { return "to-number"; },
+ [](const type::ColorType&) { return "to-color"; },
+ [](const auto&) { assert(false); return ""; });
+}
+
using namespace mbgl::style::conversion;
ParseResult Coercion::parse(const Convertible& value, ParsingContext& ctx) {
static std::unordered_map<std::string, type::Type> types {
diff --git a/src/mbgl/style/expression/compound_expression.cpp b/src/mbgl/style/expression/compound_expression.cpp
index 42cb655024..86d968c521 100644
--- a/src/mbgl/style/expression/compound_expression.cpp
+++ b/src/mbgl/style/expression/compound_expression.cpp
@@ -42,20 +42,19 @@ template <class R, class... Params>
struct Signature<R (Params...)> : SignatureBase {
using Args = std::array<std::unique_ptr<Expression>, sizeof...(Params)>;
- Signature(R (*evaluate_)(Params...)) :
+ Signature(R (*evaluate_)(Params...), std::string name_) :
SignatureBase(
valueTypeToExpressionType<std::decay_t<typename R::Value>>(),
- std::vector<type::Type> {valueTypeToExpressionType<std::decay_t<Params>>()...}
+ std::vector<type::Type> {valueTypeToExpressionType<std::decay_t<Params>>()...},
+ std::move(name_)
),
- evaluate(evaluate_)
- {}
+ evaluate(evaluate_) {}
EvaluationResult apply(const EvaluationContext& evaluationParameters, const Args& args) const {
return applyImpl(evaluationParameters, args, std::index_sequence_for<Params...>{});
}
- std::unique_ptr<Expression> makeExpression(const std::string& name,
- std::vector<std::unique_ptr<Expression>> args) const override {
+ std::unique_ptr<Expression> makeExpression(std::vector<std::unique_ptr<Expression>> args) const override {
typename Signature::Args argsArray;
std::copy_n(std::make_move_iterator(args.begin()), sizeof...(Params), argsArray.begin());
return std::make_unique<CompoundExpression<Signature>>(name, *this, std::move(argsArray));
@@ -80,16 +79,16 @@ template <class R, typename T>
struct Signature<R (const Varargs<T>&)> : SignatureBase {
using Args = std::vector<std::unique_ptr<Expression>>;
- Signature(R (*evaluate_)(const Varargs<T>&)) :
+ Signature(R (*evaluate_)(const Varargs<T>&), std::string name_) :
SignatureBase(
valueTypeToExpressionType<std::decay_t<typename R::Value>>(),
- VarargsType { valueTypeToExpressionType<T>() }
+ VarargsType { valueTypeToExpressionType<T>() },
+ std::move(name_)
),
evaluate(evaluate_)
{}
- std::unique_ptr<Expression> makeExpression(const std::string& name,
- std::vector<std::unique_ptr<Expression>> args) const override {
+ std::unique_ptr<Expression> makeExpression(std::vector<std::unique_ptr<Expression>> args) const override {
return std::make_unique<CompoundExpression<Signature>>(name, *this, std::move(args));
};
@@ -115,16 +114,16 @@ template <class R, class... Params>
struct Signature<R (const EvaluationContext&, Params...)> : SignatureBase {
using Args = std::array<std::unique_ptr<Expression>, sizeof...(Params)>;
- Signature(R (*evaluate_)(const EvaluationContext&, Params...)) :
+ Signature(R (*evaluate_)(const EvaluationContext&, Params...), std::string name_) :
SignatureBase(
valueTypeToExpressionType<std::decay_t<typename R::Value>>(),
- std::vector<type::Type> {valueTypeToExpressionType<std::decay_t<Params>>()...}
+ std::vector<type::Type> {valueTypeToExpressionType<std::decay_t<Params>>()...},
+ std::move(name_)
),
evaluate(evaluate_)
{}
- std::unique_ptr<Expression> makeExpression(const std::string& name,
- std::vector<std::unique_ptr<Expression>> args) const override {
+ std::unique_ptr<Expression> makeExpression(std::vector<std::unique_ptr<Expression>> args) const override {
typename Signature::Args argsArray;
std::copy_n(std::make_move_iterator(args.begin()), sizeof...(Params), argsArray.begin());
return std::make_unique<CompoundExpression<Signature>>(name, *this, std::move(argsArray));
@@ -176,14 +175,14 @@ struct Signature<Lambda, std::enable_if_t<std::is_class<Lambda>::value>>
using Definition = CompoundExpressionRegistry::Definition;
template <typename Fn>
-static std::unique_ptr<detail::SignatureBase> makeSignature(Fn evaluateFunction) {
- return std::make_unique<detail::Signature<Fn>>(evaluateFunction);
+static std::unique_ptr<detail::SignatureBase> makeSignature(Fn evaluateFunction, std::string name) {
+ return std::make_unique<detail::Signature<Fn>>(evaluateFunction, std::move(name));
}
std::unordered_map<std::string, CompoundExpressionRegistry::Definition> initializeDefinitions() {
std::unordered_map<std::string, CompoundExpressionRegistry::Definition> definitions;
auto define = [&](std::string name, auto fn) {
- definitions[name].push_back(makeSignature(fn));
+ definitions[name].push_back(makeSignature(fn, name));
};
define("e", []() -> Result<double> { return 2.718281828459045; });
@@ -461,7 +460,7 @@ ParseResult parseCompoundExpression(const std::string name, const Convertible& v
}
args.push_back(std::move(*parsed));
}
- return createCompoundExpression(name, definition, std::move(args), ctx);
+ return createCompoundExpression(definition, std::move(args), ctx);
}
@@ -469,12 +468,11 @@ ParseResult createCompoundExpression(const std::string& name,
std::vector<std::unique_ptr<Expression>> args,
ParsingContext& ctx)
{
- return createCompoundExpression(name, CompoundExpressionRegistry::definitions.at(name), std::move(args), ctx);
+ return createCompoundExpression(CompoundExpressionRegistry::definitions.at(name), std::move(args), ctx);
}
-ParseResult createCompoundExpression(const std::string& name,
- const Definition& definition,
+ParseResult createCompoundExpression(const Definition& definition,
std::vector<std::unique_ptr<Expression>> args,
ParsingContext& ctx)
{
@@ -512,7 +510,7 @@ ParseResult createCompoundExpression(const std::string& name,
}
if (signatureContext.getErrors().size() == 0) {
- return ParseResult(signature->makeExpression(name, std::move(args)));
+ return ParseResult(signature->makeExpression(std::move(args)));
}
}
diff --git a/src/mbgl/style/expression/interpolate.cpp b/src/mbgl/style/expression/interpolate.cpp
index 4cb22a3e4f..30b2cba81b 100644
--- a/src/mbgl/style/expression/interpolate.cpp
+++ b/src/mbgl/style/expression/interpolate.cpp
@@ -216,6 +216,30 @@ std::vector<optional<Value>> InterpolateBase::possibleOutputs() const {
return result;
}
+template <typename T>
+mbgl::Value Interpolate<T>::serialize() const {
+ std::vector<mbgl::Value> serialized;
+ serialized.emplace_back(getOperator());
+
+ interpolator.match(
+ [&](const ExponentialInterpolator& exponential) {
+ serialized.emplace_back(std::vector<mbgl::Value>{{ std::string("exponential"), exponential.base }});
+ },
+ [&](const CubicBezierInterpolator& cubicBezier) {
+ static const std::string cubicBezierTag("cubic-bezier");
+ auto p1 = cubicBezier.ub.getP1();
+ auto p2 = cubicBezier.ub.getP2();
+ serialized.emplace_back(std::vector<mbgl::Value>{{ cubicBezierTag, p1.first, p1.second, p2.first, p2.second }});
+ }
+ );
+ serialized.emplace_back(input->serialize());
+ for (auto& entry : stops) {
+ serialized.emplace_back(entry.first);
+ serialized.emplace_back(entry.second->serialize());
+ };
+ return serialized;
+}
+
} // namespace expression
} // namespace style
} // namespace mbgl
diff --git a/src/mbgl/style/expression/let.cpp b/src/mbgl/style/expression/let.cpp
index fe48138ac3..242a995b0b 100644
--- a/src/mbgl/style/expression/let.cpp
+++ b/src/mbgl/style/expression/let.cpp
@@ -65,6 +65,17 @@ ParseResult Let::parse(const Convertible& value, ParsingContext& ctx) {
return ParseResult(std::make_unique<Let>(std::move(bindings_), std::move(*result_)));
}
+mbgl::Value Let::serialize() const {
+ std::vector<mbgl::Value> serialized;
+ serialized.emplace_back(getOperator());
+ for (auto entry : bindings) {
+ serialized.emplace_back(entry.first);
+ serialized.emplace_back(entry.second->serialize());
+ }
+ serialized.emplace_back(result->serialize());
+ return serialized;
+}
+
EvaluationResult Var::evaluate(const EvaluationContext& params) const {
return value->evaluate(params);
}
@@ -95,6 +106,10 @@ ParseResult Var::parse(const Convertible& value_, ParsingContext& ctx) {
return ParseResult(std::make_unique<Var>(name_, std::move(*bindingValue)));
}
+mbgl::Value Var::serialize() const {
+ return std::vector<mbgl::Value>{{ getOperator(), name }};
+}
+
} // namespace expression
} // namespace style
} // namespace mbgl
diff --git a/src/mbgl/style/expression/literal.cpp b/src/mbgl/style/expression/literal.cpp
index 7e79fcbfe6..8a63980dba 100644
--- a/src/mbgl/style/expression/literal.cpp
+++ b/src/mbgl/style/expression/literal.cpp
@@ -102,6 +102,14 @@ ParseResult Literal::parse(const Convertible& value, ParsingContext& ctx) {
}
}
+mbgl::Value Literal::serialize() const {
+ if (getType().is<type::Array>() || getType().is<type::ObjectType>()) {
+ return std::vector<mbgl::Value>{{ getOperator(), *fromExpressionValue<mbgl::Value>(value) }};
+ } else {
+ return *fromExpressionValue<mbgl::Value>(value);
+ }
+}
+
} // namespace expression
} // namespace style
} // namespace mbgl
diff --git a/src/mbgl/style/expression/match.cpp b/src/mbgl/style/expression/match.cpp
index 0b2790b688..3d41f0bdd3 100644
--- a/src/mbgl/style/expression/match.cpp
+++ b/src/mbgl/style/expression/match.cpp
@@ -40,6 +40,42 @@ std::vector<optional<Value>> Match<T>::possibleOutputs() const {
return result;
}
+template <typename T>
+mbgl::Value Match<T>::serialize() const {
+ std::vector<mbgl::Value> serialized;
+ serialized.emplace_back(getOperator());
+ serialized.emplace_back(input->serialize());
+
+ // Sort so serialization has an arbitrary defined order, even though branch order doesn't affect evaluation
+ std::map<T, std::shared_ptr<Expression>> sortedBranches(branches.begin(), branches.end());
+
+ // Group branches by unique match expression to support condensed serializations
+ // of the form [case1, case2, ...] -> matchExpression
+ std::map<Expression*, size_t> outputLookup;
+ std::vector<std::pair<Expression*, std::vector<mbgl::Value>>> groupedByOutput;
+ for (auto& entry : sortedBranches) {
+ auto outputIndex = outputLookup.find(entry.second.get());
+ if (outputIndex == outputLookup.end()) {
+ // First time seeing this output, add it to the end of the grouped list
+ outputLookup[entry.second.get()] = groupedByOutput.size();
+ groupedByOutput.emplace_back(entry.second.get(), std::vector<mbgl::Value>{{entry.first}});
+ } else {
+ // We've seen this expression before, add the label to that output's group
+ groupedByOutput[outputIndex->second].second.emplace_back(entry.first);
+ }
+ };
+
+ for (auto& entry : groupedByOutput) {
+ entry.second.size() == 1
+ ? serialized.emplace_back(entry.second[0]) // Only a single label matches this output expression
+ : serialized.emplace_back(entry.second); // Array of literal labels pointing to this output expression
+ serialized.emplace_back(entry.first->serialize()); // The output expression itself
+ }
+
+ serialized.emplace_back(otherwise->serialize());
+ return serialized;
+}
+
template<> EvaluationResult Match<std::string>::evaluate(const EvaluationContext& params) const {
const EvaluationResult inputValue = input->evaluate(params);
diff --git a/src/mbgl/style/expression/step.cpp b/src/mbgl/style/expression/step.cpp
index 34537d48ae..ddaf9417cb 100644
--- a/src/mbgl/style/expression/step.cpp
+++ b/src/mbgl/style/expression/step.cpp
@@ -168,6 +168,18 @@ ParseResult Step::parse(const mbgl::style::conversion::Convertible& value, Parsi
return ParseResult(std::make_unique<Step>(*outputType, std::move(*input), std::move(stops)));
}
+mbgl::Value Step::serialize() const {
+ std::vector<mbgl::Value> serialized;
+ serialized.emplace_back(getOperator());
+ serialized.emplace_back(input->serialize());
+ for (auto& entry : stops) {
+ if (entry.first > -std::numeric_limits<double>::infinity()) {
+ serialized.emplace_back(entry.first);
+ }
+ serialized.emplace_back(entry.second->serialize());
+ }
+ return serialized;
+}
} // namespace expression
} // namespace style
diff --git a/src/mbgl/style/expression/value.cpp b/src/mbgl/style/expression/value.cpp
index faa44e78aa..72779d4956 100644
--- a/src/mbgl/style/expression/value.cpp
+++ b/src/mbgl/style/expression/value.cpp
@@ -103,6 +103,37 @@ Value ValueConverter<mbgl::Value>::toExpressionValue(const mbgl::Value& value) {
return mbgl::Value::visit(value, FromMBGLValue());
}
+mbgl::Value ValueConverter<mbgl::Value>::fromExpressionValue(const Value& value) {
+ return value.match(
+ [&](const Color& color)->mbgl::Value {
+ return std::vector<mbgl::Value>{
+ std::string("rgba"),
+ double(255 * color.r / color.a),
+ double(255 * color.g / color.a),
+ double(255 * color.b / color.a),
+ double(color.a)
+ };
+ },
+ [&](const std::vector<Value>& values)->mbgl::Value {
+ std::vector<mbgl::Value> converted;
+ converted.reserve(values.size());
+ for (const Value& v : values) {
+ converted.emplace_back(fromExpressionValue(v));
+ }
+ return converted;
+ },
+ [&](const std::unordered_map<std::string, Value>& values)->mbgl::Value {
+ std::unordered_map<std::string, mbgl::Value> converted;
+ converted.reserve(values.size());
+ for(const auto& entry : values) {
+ converted.emplace(entry.first, fromExpressionValue(entry.second));
+ }
+ return converted;
+ },
+ [&](const auto& a)->mbgl::Value { return a; }
+ );
+}
+
Value ValueConverter<float>::toExpressionValue(const float value) {
return static_cast<double>(value);
}
@@ -237,7 +268,7 @@ template <> type::Type valueTypeToExpressionType<type::ErrorType>() { return typ
template Value toExpressionValue(const mbgl::Value&);
-
+template optional<mbgl::Value> fromExpressionValue<mbgl::Value>(const Value&);
// for to_rgba expression
template type::Type valueTypeToExpressionType<std::array<double, 4>>();
diff --git a/src/mbgl/style/layers/heatmap_layer.cpp b/src/mbgl/style/layers/heatmap_layer.cpp
new file mode 100644
index 0000000000..4989ff15f1
--- /dev/null
+++ b/src/mbgl/style/layers/heatmap_layer.cpp
@@ -0,0 +1,239 @@
+// This file is generated. Edit scripts/generate-style-code.js, then run `make style-code`.
+
+#include <mbgl/style/layers/heatmap_layer.hpp>
+#include <mbgl/style/layers/heatmap_layer_impl.hpp>
+#include <mbgl/style/layer_observer.hpp>
+// for constructing default heatmap-color ramp expression from style JSON
+#include <mbgl/style/conversion.hpp>
+#include <mbgl/style/conversion/json.hpp>
+#include <mbgl/style/conversion/heatmap_color_property_value.hpp>
+
+namespace mbgl {
+namespace style {
+
+HeatmapLayer::HeatmapLayer(const std::string& layerID, const std::string& sourceID)
+ : Layer(makeMutable<Impl>(LayerType::Heatmap, layerID, sourceID)) {
+}
+
+HeatmapLayer::HeatmapLayer(Immutable<Impl> impl_)
+ : Layer(std::move(impl_)) {
+}
+
+HeatmapLayer::~HeatmapLayer() = default;
+
+const HeatmapLayer::Impl& HeatmapLayer::impl() const {
+ return static_cast<const Impl&>(*baseImpl);
+}
+
+Mutable<HeatmapLayer::Impl> HeatmapLayer::mutableImpl() const {
+ return makeMutable<Impl>(impl());
+}
+
+std::unique_ptr<Layer> HeatmapLayer::cloneRef(const std::string& id_) const {
+ auto impl_ = mutableImpl();
+ impl_->id = id_;
+ impl_->paint = HeatmapPaintProperties::Transitionable();
+ return std::make_unique<HeatmapLayer>(std::move(impl_));
+}
+
+void HeatmapLayer::Impl::stringifyLayout(rapidjson::Writer<rapidjson::StringBuffer>&) const {
+}
+
+// Source
+
+const std::string& HeatmapLayer::getSourceID() const {
+ return impl().source;
+}
+
+void HeatmapLayer::setSourceLayer(const std::string& sourceLayer) {
+ auto impl_ = mutableImpl();
+ impl_->sourceLayer = sourceLayer;
+ baseImpl = std::move(impl_);
+}
+
+const std::string& HeatmapLayer::getSourceLayer() const {
+ return impl().sourceLayer;
+}
+
+// Filter
+
+void HeatmapLayer::setFilter(const Filter& filter) {
+ auto impl_ = mutableImpl();
+ impl_->filter = filter;
+ baseImpl = std::move(impl_);
+ observer->onLayerChanged(*this);
+}
+
+const Filter& HeatmapLayer::getFilter() const {
+ return impl().filter;
+}
+
+// Visibility
+
+void HeatmapLayer::setVisibility(VisibilityType value) {
+ if (value == getVisibility())
+ return;
+ auto impl_ = mutableImpl();
+ impl_->visibility = value;
+ baseImpl = std::move(impl_);
+ observer->onLayerChanged(*this);
+}
+
+// Zoom range
+
+void HeatmapLayer::setMinZoom(float minZoom) {
+ auto impl_ = mutableImpl();
+ impl_->minZoom = minZoom;
+ baseImpl = std::move(impl_);
+}
+
+void HeatmapLayer::setMaxZoom(float maxZoom) {
+ auto impl_ = mutableImpl();
+ impl_->maxZoom = maxZoom;
+ baseImpl = std::move(impl_);
+}
+
+// Layout properties
+
+
+// Paint properties
+
+DataDrivenPropertyValue<float> HeatmapLayer::getDefaultHeatmapRadius() {
+ return { 30 };
+}
+
+DataDrivenPropertyValue<float> HeatmapLayer::getHeatmapRadius() const {
+ return impl().paint.template get<HeatmapRadius>().value;
+}
+
+void HeatmapLayer::setHeatmapRadius(DataDrivenPropertyValue<float> value) {
+ if (value == getHeatmapRadius())
+ return;
+ auto impl_ = mutableImpl();
+ impl_->paint.template get<HeatmapRadius>().value = value;
+ baseImpl = std::move(impl_);
+ observer->onLayerChanged(*this);
+}
+
+void HeatmapLayer::setHeatmapRadiusTransition(const TransitionOptions& options) {
+ auto impl_ = mutableImpl();
+ impl_->paint.template get<HeatmapRadius>().options = options;
+ baseImpl = std::move(impl_);
+}
+
+TransitionOptions HeatmapLayer::getHeatmapRadiusTransition() const {
+ return impl().paint.template get<HeatmapRadius>().options;
+}
+
+DataDrivenPropertyValue<float> HeatmapLayer::getDefaultHeatmapWeight() {
+ return { 1 };
+}
+
+DataDrivenPropertyValue<float> HeatmapLayer::getHeatmapWeight() const {
+ return impl().paint.template get<HeatmapWeight>().value;
+}
+
+void HeatmapLayer::setHeatmapWeight(DataDrivenPropertyValue<float> value) {
+ if (value == getHeatmapWeight())
+ return;
+ auto impl_ = mutableImpl();
+ impl_->paint.template get<HeatmapWeight>().value = value;
+ baseImpl = std::move(impl_);
+ observer->onLayerChanged(*this);
+}
+
+void HeatmapLayer::setHeatmapWeightTransition(const TransitionOptions& options) {
+ auto impl_ = mutableImpl();
+ impl_->paint.template get<HeatmapWeight>().options = options;
+ baseImpl = std::move(impl_);
+}
+
+TransitionOptions HeatmapLayer::getHeatmapWeightTransition() const {
+ return impl().paint.template get<HeatmapWeight>().options;
+}
+
+PropertyValue<float> HeatmapLayer::getDefaultHeatmapIntensity() {
+ return { 1 };
+}
+
+PropertyValue<float> HeatmapLayer::getHeatmapIntensity() const {
+ return impl().paint.template get<HeatmapIntensity>().value;
+}
+
+void HeatmapLayer::setHeatmapIntensity(PropertyValue<float> value) {
+ if (value == getHeatmapIntensity())
+ return;
+ auto impl_ = mutableImpl();
+ impl_->paint.template get<HeatmapIntensity>().value = value;
+ baseImpl = std::move(impl_);
+ observer->onLayerChanged(*this);
+}
+
+void HeatmapLayer::setHeatmapIntensityTransition(const TransitionOptions& options) {
+ auto impl_ = mutableImpl();
+ impl_->paint.template get<HeatmapIntensity>().options = options;
+ baseImpl = std::move(impl_);
+}
+
+TransitionOptions HeatmapLayer::getHeatmapIntensityTransition() const {
+ return impl().paint.template get<HeatmapIntensity>().options;
+}
+
+HeatmapColorPropertyValue HeatmapLayer::getDefaultHeatmapColor() {
+ conversion::Error error;
+ std::string rawValue = R"JSON(["interpolate",["linear"],["heatmap-density"],0,"rgba(0, 0, 255, 0)",0.1,"royalblue",0.3,"cyan",0.5,"lime",0.7,"yellow",1,"red"])JSON";
+ return *conversion::convertJSON<HeatmapColorPropertyValue>(rawValue, error);
+}
+
+HeatmapColorPropertyValue HeatmapLayer::getHeatmapColor() const {
+ return impl().paint.template get<HeatmapColor>().value;
+}
+
+void HeatmapLayer::setHeatmapColor(HeatmapColorPropertyValue value) {
+ if (value == getHeatmapColor())
+ return;
+ auto impl_ = mutableImpl();
+ impl_->paint.template get<HeatmapColor>().value = value;
+ baseImpl = std::move(impl_);
+ observer->onLayerChanged(*this);
+}
+
+void HeatmapLayer::setHeatmapColorTransition(const TransitionOptions& options) {
+ auto impl_ = mutableImpl();
+ impl_->paint.template get<HeatmapColor>().options = options;
+ baseImpl = std::move(impl_);
+}
+
+TransitionOptions HeatmapLayer::getHeatmapColorTransition() const {
+ return impl().paint.template get<HeatmapColor>().options;
+}
+
+PropertyValue<float> HeatmapLayer::getDefaultHeatmapOpacity() {
+ return { 1 };
+}
+
+PropertyValue<float> HeatmapLayer::getHeatmapOpacity() const {
+ return impl().paint.template get<HeatmapOpacity>().value;
+}
+
+void HeatmapLayer::setHeatmapOpacity(PropertyValue<float> value) {
+ if (value == getHeatmapOpacity())
+ return;
+ auto impl_ = mutableImpl();
+ impl_->paint.template get<HeatmapOpacity>().value = value;
+ baseImpl = std::move(impl_);
+ observer->onLayerChanged(*this);
+}
+
+void HeatmapLayer::setHeatmapOpacityTransition(const TransitionOptions& options) {
+ auto impl_ = mutableImpl();
+ impl_->paint.template get<HeatmapOpacity>().options = options;
+ baseImpl = std::move(impl_);
+}
+
+TransitionOptions HeatmapLayer::getHeatmapOpacityTransition() const {
+ return impl().paint.template get<HeatmapOpacity>().options;
+}
+
+} // namespace style
+} // namespace mbgl
diff --git a/src/mbgl/style/layers/heatmap_layer_impl.cpp b/src/mbgl/style/layers/heatmap_layer_impl.cpp
new file mode 100644
index 0000000000..af20888d9d
--- /dev/null
+++ b/src/mbgl/style/layers/heatmap_layer_impl.cpp
@@ -0,0 +1,15 @@
+#include <mbgl/style/layers/heatmap_layer_impl.hpp>
+
+namespace mbgl {
+namespace style {
+
+bool HeatmapLayer::Impl::hasLayoutDifference(const Layer::Impl& other) const {
+ assert(dynamic_cast<const HeatmapLayer::Impl*>(&other));
+ const auto& impl = static_cast<const style::HeatmapLayer::Impl&>(other);
+ return filter != impl.filter ||
+ visibility != impl.visibility ||
+ paint.hasDataDrivenPropertyDifference(impl.paint);
+}
+
+} // namespace style
+} // namespace mbgl
diff --git a/src/mbgl/style/layers/heatmap_layer_impl.hpp b/src/mbgl/style/layers/heatmap_layer_impl.hpp
new file mode 100644
index 0000000000..cc27c3076a
--- /dev/null
+++ b/src/mbgl/style/layers/heatmap_layer_impl.hpp
@@ -0,0 +1,21 @@
+#pragma once
+
+#include <mbgl/style/layer_impl.hpp>
+#include <mbgl/style/layers/heatmap_layer.hpp>
+#include <mbgl/style/layers/heatmap_layer_properties.hpp>
+
+namespace mbgl {
+namespace style {
+
+class HeatmapLayer::Impl : public Layer::Impl {
+public:
+ using Layer::Impl::Impl;
+
+ bool hasLayoutDifference(const Layer::Impl&) const override;
+ void stringifyLayout(rapidjson::Writer<rapidjson::StringBuffer>&) const override;
+
+ HeatmapPaintProperties::Transitionable paint;
+};
+
+} // namespace style
+} // namespace mbgl
diff --git a/src/mbgl/style/layers/heatmap_layer_properties.cpp b/src/mbgl/style/layers/heatmap_layer_properties.cpp
new file mode 100644
index 0000000000..2edb839589
--- /dev/null
+++ b/src/mbgl/style/layers/heatmap_layer_properties.cpp
@@ -0,0 +1,9 @@
+// This file is generated. Edit scripts/generate-style-code.js, then run `make style-code`.
+
+#include <mbgl/style/layers/heatmap_layer_properties.hpp>
+
+namespace mbgl {
+namespace style {
+
+} // namespace style
+} // namespace mbgl
diff --git a/src/mbgl/style/layers/heatmap_layer_properties.hpp b/src/mbgl/style/layers/heatmap_layer_properties.hpp
new file mode 100644
index 0000000000..f7afa5fbeb
--- /dev/null
+++ b/src/mbgl/style/layers/heatmap_layer_properties.hpp
@@ -0,0 +1,40 @@
+// This file is generated. Edit scripts/generate-style-code.js, then run `make style-code`.
+
+#pragma once
+
+#include <mbgl/style/types.hpp>
+#include <mbgl/style/layout_property.hpp>
+#include <mbgl/style/paint_property.hpp>
+#include <mbgl/style/properties.hpp>
+#include <mbgl/programs/attributes.hpp>
+#include <mbgl/programs/uniforms.hpp>
+
+namespace mbgl {
+namespace style {
+
+struct HeatmapRadius : DataDrivenPaintProperty<float, attributes::a_radius, uniforms::u_radius> {
+ static float defaultValue() { return 30; }
+};
+
+struct HeatmapWeight : DataDrivenPaintProperty<float, attributes::a_weight, uniforms::u_weight> {
+ static float defaultValue() { return 1; }
+};
+
+struct HeatmapIntensity : PaintProperty<float> {
+ static float defaultValue() { return 1; }
+};
+
+struct HeatmapOpacity : PaintProperty<float> {
+ static float defaultValue() { return 1; }
+};
+
+class HeatmapPaintProperties : public Properties<
+ HeatmapRadius,
+ HeatmapWeight,
+ HeatmapIntensity,
+ HeatmapColor,
+ HeatmapOpacity
+> {};
+
+} // namespace style
+} // namespace mbgl
diff --git a/src/mbgl/style/layers/layer.cpp.ejs b/src/mbgl/style/layers/layer.cpp.ejs
index be44bb353d..657a7f5a8a 100644
--- a/src/mbgl/style/layers/layer.cpp.ejs
+++ b/src/mbgl/style/layers/layer.cpp.ejs
@@ -8,6 +8,12 @@
#include <mbgl/style/layers/<%- type.replace('-', '_') %>_layer.hpp>
#include <mbgl/style/layers/<%- type.replace('-', '_') %>_layer_impl.hpp>
#include <mbgl/style/layer_observer.hpp>
+<% if (type === 'heatmap') { -%>
+// for constructing default heatmap-color ramp expression from style JSON
+#include <mbgl/style/conversion.hpp>
+#include <mbgl/style/conversion/json.hpp>
+#include <mbgl/style/conversion/heatmap_color_property_value.hpp>
+<% } -%>
namespace mbgl {
namespace style {
@@ -134,7 +140,13 @@ void <%- camelize(type) %>Layer::set<%- camelize(property.name) %>(<%- propertyV
// Paint properties
<% for (const property of paintProperties) { %>
<%- propertyValueType(property) %> <%- camelize(type) %>Layer::getDefault<%- camelize(property.name) %>() {
+<% if (property.name === 'heatmap-color') { -%>
+ conversion::Error error;
+ std::string rawValue = R"JSON(<%- JSON.stringify(property.default) %>)JSON";
+ return *conversion::convertJSON<<%- propertyValueType(property)%>>(rawValue, error);
+<% } else { -%>
return { <%- defaultValue(property) %> };
+<% } -%>
}
<%- propertyValueType(property) %> <%- camelize(type) %>Layer::get<%- camelize(property.name) %>() const {
diff --git a/src/mbgl/style/layers/layer_properties.hpp.ejs b/src/mbgl/style/layers/layer_properties.hpp.ejs
index cde1b80b7b..1bceb84960 100644
--- a/src/mbgl/style/layers/layer_properties.hpp.ejs
+++ b/src/mbgl/style/layers/layer_properties.hpp.ejs
@@ -25,6 +25,7 @@ struct <%- camelize(property.name) %> : <%- layoutPropertyType(property, type) %
<% } -%>
<% for (const property of paintProperties) { -%>
+<% if (property.name === 'heatmap-color') continue; -%>
struct <%- camelize(property.name) %> : <%- paintPropertyType(property, type) %> {
static <%- evaluatedType(property) %> defaultValue() { return <%- defaultValue(property) %>; }
};
diff --git a/src/mbgl/style/paint_property.hpp b/src/mbgl/style/paint_property.hpp
index c4c996b3bd..195eb645a9 100644
--- a/src/mbgl/style/paint_property.hpp
+++ b/src/mbgl/style/paint_property.hpp
@@ -2,6 +2,7 @@
#include <mbgl/style/properties.hpp>
#include <mbgl/style/property_value.hpp>
+#include <mbgl/style/heatmap_color_property_value.hpp>
#include <mbgl/style/data_driven_property_value.hpp>
#include <mbgl/renderer/property_evaluator.hpp>
#include <mbgl/renderer/cross_faded_property_evaluator.hpp>
@@ -48,5 +49,27 @@ public:
static constexpr bool IsDataDriven = false;
};
+/*
+ * Special-case paint property traits for heatmap-color, needed because
+ * heatmap-color values do not fit into the
+ * Undefined | Value | {Camera,Source,Composite}Function taxonomy that applies
+ * to all other paint properties.
+ *
+ * These traits are provided here--despite the fact that heatmap-color
+ * is not used like other paint properties--to allow the parameter-pack-based
+ * batch evaluation of paint properties to compile properly.
+ */
+class HeatmapColor {
+public:
+ using TransitionableType = Transitionable<HeatmapColorPropertyValue>;
+ using UnevaluatedType = Transitioning<HeatmapColorPropertyValue>;
+ using EvaluatorType = PropertyEvaluator<Color>;
+ using PossiblyEvaluatedType = Color;
+ using Type = Color;
+ static constexpr bool IsDataDriven = false;
+
+ static Color defaultValue() { return {}; }
+};
+
} // namespace style
} // namespace mbgl
diff --git a/src/mbgl/style/style_impl.cpp b/src/mbgl/style/style_impl.cpp
index f2a9c0f222..0c7f924917 100644
--- a/src/mbgl/style/style_impl.cpp
+++ b/src/mbgl/style/style_impl.cpp
@@ -6,6 +6,7 @@
#include <mbgl/style/layers/background_layer.hpp>
#include <mbgl/style/layers/fill_layer.hpp>
#include <mbgl/style/layers/fill_extrusion_layer.hpp>
+#include <mbgl/style/layers/heatmap_layer.hpp>
#include <mbgl/style/layers/line_layer.hpp>
#include <mbgl/style/layers/circle_layer.hpp>
#include <mbgl/style/layers/raster_layer.hpp>
@@ -54,11 +55,6 @@ void Style::Impl::loadURL(const std::string& url_) {
url = url_;
styleRequest = fileSource.request(Resource::style(url), [this](Response res) {
- // Once we get a fresh style, or the style is mutated, stop revalidating.
- if (res.isFresh() || mutated) {
- styleRequest.reset();
- }
-
// Don't allow a loaded, mutated style to be overwritten with a new version.
if (mutated && loaded) {
return;
diff --git a/src/mbgl/text/placement.cpp b/src/mbgl/text/placement.cpp
index 400fe79ac5..54b2b7539b 100644
--- a/src/mbgl/text/placement.cpp
+++ b/src/mbgl/text/placement.cpp
@@ -95,9 +95,6 @@ void Placement::placeLayerBucket(
auto partiallyEvaluatedTextSize = bucket.textSizeBinder->evaluateForZoom(state.getZoom());
auto partiallyEvaluatedIconSize = bucket.iconSizeBinder->evaluateForZoom(state.getZoom());
- const bool iconWithoutText = !bucket.hasTextData() || bucket.layout.get<style::TextOptional>();
- const bool textWithoutIcon = !bucket.hasIconData() || bucket.layout.get<style::IconOptional>();
-
for (auto& symbolInstance : bucket.symbolInstances) {
if (seenCrossTileIDs.count(symbolInstance.crossTileID) == 0) {
@@ -140,6 +137,9 @@ void Placement::placeLayerBucket(
offscreen &= placed.second;
}
+ const bool iconWithoutText = !symbolInstance.hasText || bucket.layout.get<style::TextOptional>();
+ const bool textWithoutIcon = !symbolInstance.hasIcon || bucket.layout.get<style::IconOptional>();
+
// combine placements for icon and text
if (!iconWithoutText && !textWithoutIcon) {
placeText = placeIcon = placeText && placeIcon;
@@ -230,6 +230,8 @@ void Placement::updateBucketOpacities(SymbolBucket& bucket, std::set<uint32_t>&
if (bucket.hasCollisionBoxData()) bucket.collisionBox.dynamicVertices.clear();
if (bucket.hasCollisionCircleData()) bucket.collisionCircle.dynamicVertices.clear();
+ JointOpacityState duplicateOpacityState(false, false, true);
+
JointOpacityState defaultOpacityState(
bucket.layout.get<style::TextAllowOverlap>(),
bucket.layout.get<style::IconAllowOverlap>(),
@@ -239,9 +241,12 @@ void Placement::updateBucketOpacities(SymbolBucket& bucket, std::set<uint32_t>&
bool isDuplicate = seenCrossTileIDs.count(symbolInstance.crossTileID) > 0;
auto it = opacities.find(symbolInstance.crossTileID);
- auto opacityState = it != opacities.end() && !isDuplicate ?
- it->second :
- defaultOpacityState;
+ auto opacityState = defaultOpacityState;
+ if (isDuplicate) {
+ opacityState = duplicateOpacityState;
+ } else if (it != opacities.end()) {
+ opacityState = it->second;
+ }
if (it == opacities.end()) {
opacities.emplace(symbolInstance.crossTileID, defaultOpacityState);
diff --git a/src/mbgl/tile/custom_geometry_tile.cpp b/src/mbgl/tile/custom_geometry_tile.cpp
index 7c8a85c4a4..33962ad87d 100644
--- a/src/mbgl/tile/custom_geometry_tile.cpp
+++ b/src/mbgl/tile/custom_geometry_tile.cpp
@@ -39,7 +39,7 @@ void CustomGeometryTile::setTileData(const GeoJSON& geoJSON) {
vtOptions.extent = util::EXTENT;
vtOptions.buffer = ::round(scale * options.buffer);
vtOptions.tolerance = scale * options.tolerance;
- featureData = mapbox::geojsonvt::geoJSONToTile(geoJSON, id.canonical.z, id.canonical.x, id.canonical.y, vtOptions).features;
+ featureData = mapbox::geojsonvt::geoJSONToTile(geoJSON, id.canonical.z, id.canonical.x, id.canonical.y, vtOptions, options.wrap, options.clip).features;
} else {
setNecessity(TileNecessity::Optional);
}
diff --git a/src/mbgl/tile/raster_dem_tile.cpp b/src/mbgl/tile/raster_dem_tile.cpp
index b270378ece..5db298cf4c 100644
--- a/src/mbgl/tile/raster_dem_tile.cpp
+++ b/src/mbgl/tile/raster_dem_tile.cpp
@@ -21,6 +21,7 @@ RasterDEMTile::RasterDEMTile(const OverscaledTileID& id_,
worker(parameters.workerScheduler,
ActorRef<RasterDEMTile>(*this, mailbox)) {
+ encoding = tileset.encoding;
if ( id.canonical.y == 0 ){
// this tile doesn't have upper neighboring tiles so marked those as backfilled
neighboringTiles = neighboringTiles | DEMTileNeighbors::NoUpper;
@@ -47,7 +48,7 @@ void RasterDEMTile::setMetadata(optional<Timestamp> modified_, optional<Timestam
void RasterDEMTile::setData(std::shared_ptr<const std::string> data) {
pending = true;
++correlationID;
- worker.invoke(&RasterDEMTileWorker::parse, data, correlationID);
+ worker.invoke(&RasterDEMTileWorker::parse, data, correlationID, encoding);
}
void RasterDEMTile::onParsed(std::unique_ptr<HillshadeBucket> result, const uint64_t resultCorrelationID) {
diff --git a/src/mbgl/tile/raster_dem_tile.hpp b/src/mbgl/tile/raster_dem_tile.hpp
index 68f8a91e00..0c8dd75961 100644
--- a/src/mbgl/tile/raster_dem_tile.hpp
+++ b/src/mbgl/tile/raster_dem_tile.hpp
@@ -94,6 +94,7 @@ private:
Actor<RasterDEMTileWorker> worker;
uint64_t correlationID = 0;
+ Tileset::DEMEncoding encoding;
// Contains the Bucket object for the tile. Buckets are render
// objects and they get added by tile parsing operations.
diff --git a/src/mbgl/tile/raster_dem_tile_worker.cpp b/src/mbgl/tile/raster_dem_tile_worker.cpp
index ed8573788f..7338e578c7 100644
--- a/src/mbgl/tile/raster_dem_tile_worker.cpp
+++ b/src/mbgl/tile/raster_dem_tile_worker.cpp
@@ -10,14 +10,14 @@ RasterDEMTileWorker::RasterDEMTileWorker(ActorRef<RasterDEMTileWorker>, ActorRef
: parent(std::move(parent_)) {
}
-void RasterDEMTileWorker::parse(std::shared_ptr<const std::string> data, uint64_t correlationID) {
+void RasterDEMTileWorker::parse(std::shared_ptr<const std::string> data, uint64_t correlationID, Tileset::DEMEncoding encoding) {
if (!data) {
parent.invoke(&RasterDEMTile::onParsed, nullptr, correlationID); // No data; empty tile.
return;
}
try {
- auto bucket = std::make_unique<HillshadeBucket>(decodeImage(*data));
+ auto bucket = std::make_unique<HillshadeBucket>(decodeImage(*data), encoding);
parent.invoke(&RasterDEMTile::onParsed, std::move(bucket), correlationID);
} catch (...) {
parent.invoke(&RasterDEMTile::onError, std::current_exception(), correlationID);
diff --git a/src/mbgl/tile/raster_dem_tile_worker.hpp b/src/mbgl/tile/raster_dem_tile_worker.hpp
index 14fd1f43b5..5a8222bc2d 100644
--- a/src/mbgl/tile/raster_dem_tile_worker.hpp
+++ b/src/mbgl/tile/raster_dem_tile_worker.hpp
@@ -1,6 +1,7 @@
#pragma once
#include <mbgl/actor/actor_ref.hpp>
+#include <mbgl/util/tileset.hpp>
#include <memory>
#include <string>
@@ -13,7 +14,7 @@ class RasterDEMTileWorker {
public:
RasterDEMTileWorker(ActorRef<RasterDEMTileWorker>, ActorRef<RasterDEMTile>);
- void parse(std::shared_ptr<const std::string> data, uint64_t correlationID);
+ void parse(std::shared_ptr<const std::string> data, uint64_t correlationID, Tileset::DEMEncoding encoding);
private:
ActorRef<RasterDEMTile> parent;
diff --git a/src/mbgl/util/offscreen_texture.cpp b/src/mbgl/util/offscreen_texture.cpp
index 339e74b250..03f555eae0 100644
--- a/src/mbgl/util/offscreen_texture.cpp
+++ b/src/mbgl/util/offscreen_texture.cpp
@@ -11,20 +11,21 @@ OffscreenTexture& OffscreenTexture::operator=(OffscreenTexture&&) = default;
class OffscreenTexture::Impl {
public:
- Impl(gl::Context& context_, const Size size_)
- : context(context_), size(std::move(size_)) {
+ Impl(gl::Context& context_, const Size size_, const gl::TextureType type_)
+ : context(context_), size(std::move(size_)), type(type_) {
assert(!size.isEmpty());
}
Impl(gl::Context& context_,
const Size size_,
- gl::Renderbuffer<gl::RenderbufferType::DepthComponent>& depth_)
- : context(context_), size(std::move(size_)), depth(&depth_) {
+ gl::Renderbuffer<gl::RenderbufferType::DepthComponent>& depth_,
+ const gl::TextureType type_)
+ : context(context_), size(std::move(size_)), depth(&depth_), type(type_) {
assert(!size.isEmpty());
}
void bind() {
if (!framebuffer) {
- texture = context.createTexture(size, gl::TextureFormat::RGBA);
+ texture = context.createTexture(size, gl::TextureFormat::RGBA, 0, type);
if (depth) {
framebuffer = context.createFramebuffer(*texture, *depth);
} else {
@@ -58,18 +59,21 @@ private:
optional<gl::Framebuffer> framebuffer;
optional<gl::Texture> texture;
gl::Renderbuffer<gl::RenderbufferType::DepthComponent>* depth = nullptr;
+ const gl::TextureType type;
};
OffscreenTexture::OffscreenTexture(gl::Context& context,
- const Size size)
- : impl(std::make_unique<Impl>(context, std::move(size))) {
+ const Size size,
+ const gl::TextureType type)
+ : impl(std::make_unique<Impl>(context, std::move(size), type)) {
assert(!size.isEmpty());
}
OffscreenTexture::OffscreenTexture(gl::Context& context,
const Size size,
- gl::Renderbuffer<gl::RenderbufferType::DepthComponent>& renderbuffer)
- : impl(std::make_unique<Impl>(context, std::move(size), renderbuffer)) {
+ gl::Renderbuffer<gl::RenderbufferType::DepthComponent>& renderbuffer,
+ const gl::TextureType type)
+ : impl(std::make_unique<Impl>(context, std::move(size), renderbuffer, type)) {
assert(!size.isEmpty());
}
diff --git a/src/mbgl/util/offscreen_texture.hpp b/src/mbgl/util/offscreen_texture.hpp
index 7f7e0f0338..36f24f16d3 100644
--- a/src/mbgl/util/offscreen_texture.hpp
+++ b/src/mbgl/util/offscreen_texture.hpp
@@ -12,10 +12,12 @@ class Texture;
class OffscreenTexture {
public:
OffscreenTexture(gl::Context&,
- Size size = { 256, 256 });
+ Size size = { 256, 256 },
+ gl::TextureType type = gl::TextureType::UnsignedByte);
OffscreenTexture(gl::Context&,
Size size,
- gl::Renderbuffer<gl::RenderbufferType::DepthComponent>&);
+ gl::Renderbuffer<gl::RenderbufferType::DepthComponent>&,
+ gl::TextureType type = gl::TextureType::UnsignedByte);
~OffscreenTexture();
OffscreenTexture(OffscreenTexture&&);
OffscreenTexture& operator=(OffscreenTexture&&);
diff --git a/src/mbgl/util/tile_range.hpp b/src/mbgl/util/tile_range.hpp
index f630a49078..8554cfb65e 100644
--- a/src/mbgl/util/tile_range.hpp
+++ b/src/mbgl/util/tile_range.hpp
@@ -6,41 +6,61 @@
#include <mbgl/util/projection.hpp>
namespace mbgl {
-
namespace util {
class TileRange {
public:
- Range<Point<double>> range;
- uint8_t z;
+ Range<Point<uint32_t>> range;
+ Range<uint8_t> zoomRange;
+
+ // Compute the range of tiles covered by the bounds at maxZoom.
+ static TileRange fromLatLngBounds(const LatLngBounds& bounds, uint8_t minZoom, uint8_t maxZoom) {
+ if (minZoom > maxZoom) {
+ std::swap(minZoom, maxZoom);
+ }
+
+ auto swProj = Projection::project(bounds.southwest().wrapped(), maxZoom);
+ auto ne = bounds.northeast();
+ auto neProj = Projection::project(ne.longitude() > util::LONGITUDE_MAX ? ne.wrapped() : ne , maxZoom);
+
+ const auto maxTile = std::pow(2.0, maxZoom);
+ const auto minX = static_cast<uint32_t>(std::floor(swProj.x));
+ const auto maxX = static_cast<uint32_t>(std::floor(neProj.x));
+ const auto minY = static_cast<uint32_t>(util::clamp(std::floor(neProj.y), 0.0 , maxTile));
+ const auto maxY = static_cast<uint32_t>(util::clamp(std::floor(swProj.y), 0.0, maxTile));
+
+ return TileRange({ {minX, minY}, {maxX, maxY} }, {minZoom, maxZoom});
+ }
// Compute the range of tiles covered by the bounds.
static TileRange fromLatLngBounds(const LatLngBounds& bounds, uint8_t z) {
- auto swProj = Projection::project(bounds.southwest().wrapped(), z);
- auto ne = bounds.northeast();
- auto neProj = Projection::project(ne.longitude() > util::LONGITUDE_MAX ? ne.wrapped() : ne , z);
- const auto minX = std::floor(swProj.x);
- const auto maxX = std::ceil(neProj.x);
- const auto minY = std::floor(neProj.y);
- const auto maxY = std::ceil(swProj.y);
- return TileRange({ {minX, minY}, {maxX, maxY} }, z);
+ return fromLatLngBounds(bounds, z, z);
}
bool contains(const CanonicalTileID& tileID) {
- return z == tileID.z &&
- (range.min.x >= range.max.x ? //For wrapped bounds
- tileID.x >= range.min.x || tileID.x < range.max.x :
- tileID.x < range.max.x && tileID.x >= range.min.x) &&
- tileID.y < range.max.y &&
- tileID.y >= range.min.y;
+ if (tileID.z <= zoomRange.max && tileID.z >= zoomRange.min) {
+ if (tileID.z == 0) {
+ return true;
+ }
+ uint8_t dz = (zoomRange.max - tileID.z);
+ auto x0 = range.min.x >> dz;
+ auto x1 = range.max.x >> dz;
+ auto y0 = range.min.y >> dz;
+ auto y1 = range.max.y >> dz;
+ return (range.min.x > range.max.x ? //For wrapped bounds
+ tileID.x >= x0 || tileID.x <= x1 :
+ tileID.x <= x1 && tileID.x >= x0) &&
+ tileID.y <= y1 &&
+ tileID.y >= y0;
+ }
+ return false;
}
private:
- TileRange(Range<Point<double>> range_, uint8_t z_)
+ TileRange(Range<Point<uint32_t>> range_, Range<uint8_t> z_)
: range(range_),
- z(z_) {
+ zoomRange(z_) {
}
-
};
} // namespace util
diff --git a/src/mbgl/util/tiny_sdf.cpp b/src/mbgl/util/tiny_sdf.cpp
index 60839357d5..6edcd83bc2 100644
--- a/src/mbgl/util/tiny_sdf.cpp
+++ b/src/mbgl/util/tiny_sdf.cpp
@@ -95,7 +95,7 @@ AlphaImage transformRasterToSDF(const AlphaImage& rasterInput, double radius, do
for (uint32_t i = 0; i < size; i++) {
double distance = gridOuter[i] - gridInner[i];
- sdf.data[i] = std::max(0l, std::min(255l, std::lround(255.0 - 255.0 * (distance / radius + cutoff))));
+ sdf.data[i] = std::max(0l, std::min(255l, ::lround(255.0 - 255.0 * (distance / radius + cutoff))));
}
return sdf;
diff --git a/test/fixtures/map/prefetch/expected.png b/test/fixtures/map/prefetch/expected.png
deleted file mode 100644
index e1111b37f7..0000000000
--- a/test/fixtures/map/prefetch/expected.png
+++ /dev/null
Binary files differ
diff --git a/test/fixtures/map/prefetch/tile_green.png b/test/fixtures/map/prefetch/tile.png
index 553cd10cd1..553cd10cd1 100644
--- a/test/fixtures/map/prefetch/tile_green.png
+++ b/test/fixtures/map/prefetch/tile.png
Binary files differ
diff --git a/test/fixtures/map/prefetch/tile_red.png b/test/fixtures/map/prefetch/tile_red.png
deleted file mode 100644
index 5fa561fb92..0000000000
--- a/test/fixtures/map/prefetch/tile_red.png
+++ /dev/null
Binary files differ
diff --git a/test/fixtures/offline_database/satellite_test.db b/test/fixtures/offline_database/satellite_test.db
new file mode 100644
index 0000000000..95dd8617ff
--- /dev/null
+++ b/test/fixtures/offline_database/satellite_test.db
Binary files differ
diff --git a/test/geometry/dem_data.test.cpp b/test/geometry/dem_data.test.cpp
index 30091973b2..3848f028f1 100644
--- a/test/geometry/dem_data.test.cpp
+++ b/test/geometry/dem_data.test.cpp
@@ -1,6 +1,7 @@
#include <mbgl/test/util.hpp>
#include <mbgl/util/image.hpp>
+#include <mbgl/util/tileset.hpp>
#include <mbgl/geometry/dem_data.hpp>
using namespace mbgl;
@@ -14,30 +15,38 @@ auto fakeImage = [](Size s) {
return img;
};
-TEST(DEMData, Constructor) {
+TEST(DEMData, ConstructorMapbox) {
PremultipliedImage image = fakeImage({16, 16});
- DEMData pyramid(image);
-
- EXPECT_EQ(pyramid.dim, 16);
- EXPECT_EQ(pyramid.border, 8);
- EXPECT_EQ(pyramid.stride, 32);
- EXPECT_EQ(pyramid.getImage()->bytes(), size_t(32*32*4));
- EXPECT_EQ(pyramid.dim, 16);
- EXPECT_EQ(pyramid.border, 8);
+ DEMData demdata(image, Tileset::DEMEncoding::Mapbox);
+
+ EXPECT_EQ(demdata.dim, 16);
+ EXPECT_EQ(demdata.border, 8);
+ EXPECT_EQ(demdata.stride, 32);
+ EXPECT_EQ(demdata.getImage()->bytes(), size_t(32*32*4));
+};
+
+TEST(DEMData, ConstructorTerrarium) {
+ PremultipliedImage image = fakeImage({16, 16});
+ DEMData demdata(image, Tileset::DEMEncoding::Terrarium);
+
+ EXPECT_EQ(demdata.dim, 16);
+ EXPECT_EQ(demdata.border, 8);
+ EXPECT_EQ(demdata.stride, 32);
+ EXPECT_EQ(demdata.getImage()->bytes(), size_t(32*32*4));
};
TEST(DEMData, RoundTrip) {
PremultipliedImage image = fakeImage({16, 16});
- DEMData pyramid(image);
+ DEMData demdata(image, Tileset::DEMEncoding::Mapbox);
- pyramid.set(4, 6, 255);
- EXPECT_EQ(pyramid.get(4, 6), 255);
+ demdata.set(4, 6, 255);
+ EXPECT_EQ(demdata.get(4, 6), 255);
}
TEST(DEMData, InitialBackfill) {
PremultipliedImage image1 = fakeImage({4, 4});
- DEMData dem1(image1);
+ DEMData dem1(image1, Tileset::DEMEncoding::Mapbox);
bool nonempty = true;
// checking that a 1 px border around the fake image has been populated
@@ -91,10 +100,10 @@ TEST(DEMData, InitialBackfill) {
TEST(DEMData, BackfillNeighbor) {
PremultipliedImage image1 = fakeImage({4, 4});
- DEMData dem0(image1);
+ DEMData dem0(image1, Tileset::DEMEncoding::Mapbox);
PremultipliedImage image2 = fakeImage({4, 4});
- DEMData dem1(image2);
+ DEMData dem1(image2, Tileset::DEMEncoding::Mapbox);
dem0.backfillBorder(dem1, -1, 0);
for (int y = 0; y < 4; y++) {
diff --git a/test/map/map.test.cpp b/test/map/map.test.cpp
index 9b34ea89b0..f95e26fd82 100644
--- a/test/map/map.test.cpp
+++ b/test/map/map.test.cpp
@@ -1,5 +1,6 @@
#include <mbgl/test/util.hpp>
#include <mbgl/test/stub_file_source.hpp>
+#include <mbgl/test/stub_map_observer.hpp>
#include <mbgl/test/fake_file_source.hpp>
#include <mbgl/test/fixture_log_observer.hpp>
@@ -23,45 +24,6 @@ using namespace mbgl;
using namespace mbgl::style;
using namespace std::literals::string_literals;
-class StubMapObserver : public MapObserver {
-public:
- void onWillStartLoadingMap() final {
- if (onWillStartLoadingMapCallback) {
- onWillStartLoadingMapCallback();
- }
- }
-
- void onDidFinishLoadingMap() final {
- if (onDidFinishLoadingMapCallback) {
- onDidFinishLoadingMapCallback();
- }
- }
-
- void onDidFailLoadingMap(std::exception_ptr) final {
- if (didFailLoadingMapCallback) {
- didFailLoadingMapCallback();
- }
- }
-
- void onDidFinishLoadingStyle() final {
- if (didFinishLoadingStyleCallback) {
- didFinishLoadingStyleCallback();
- }
- }
-
- void onDidFinishRenderingFrame(RenderMode mode) final {
- if (didFinishRenderingFrame) {
- didFinishRenderingFrame(mode);
- }
- }
-
- std::function<void()> onWillStartLoadingMapCallback;
- std::function<void()> onDidFinishLoadingMapCallback;
- std::function<void()> didFailLoadingMapCallback;
- std::function<void()> didFinishLoadingStyleCallback;
- std::function<void(RenderMode)> didFinishRenderingFrame;
-};
-
template <class FileSource = StubFileSource>
class MapTest {
public:
@@ -252,7 +214,7 @@ TEST(Map, DoubleStyleLoad) {
}
TEST(Map, StyleFresh) {
- // The map should not revalidate fresh styles.
+ // The map should continue to revalidate fresh styles.
MapTest<FakeFileSource> test;
@@ -264,11 +226,11 @@ TEST(Map, StyleFresh) {
response.expires = Timestamp::max();
test.fileSource.respond(Resource::Style, response);
- EXPECT_EQ(0u, test.fileSource.requests.size());
+ EXPECT_EQ(1u, test.fileSource.requests.size());
}
TEST(Map, StyleExpired) {
- // The map should allow expired styles to be revalidated, so long as no mutations are made.
+ // The map should allow expired styles to be revalidated until we get a fresh style.
using namespace std::chrono_literals;
@@ -284,11 +246,22 @@ TEST(Map, StyleExpired) {
test.fileSource.respond(Resource::Style, response);
EXPECT_EQ(1u, test.fileSource.requests.size());
+ // Mutate layer. From now on, sending a response to the style won't overwrite it anymore, but
+ // we should continue to wait for a fresh response.
test.map.getStyle().addLayer(std::make_unique<style::BackgroundLayer>("bg"));
EXPECT_EQ(1u, test.fileSource.requests.size());
+ // Send another expired response, and confirm that we didn't overwrite the style, but continue
+ // to wait for a fresh response.
test.fileSource.respond(Resource::Style, response);
- EXPECT_EQ(0u, test.fileSource.requests.size());
+ EXPECT_EQ(1u, test.fileSource.requests.size());
+ EXPECT_NE(nullptr, test.map.getStyle().getLayer("bg"));
+
+ // Send a fresh response, and confirm that we didn't overwrite the style, but continue to wait
+ // for a fresh response.
+ response.expires = util::now() + 1h;
+ test.fileSource.respond(Resource::Style, response);
+ EXPECT_EQ(1u, test.fileSource.requests.size());
EXPECT_NE(nullptr, test.map.getStyle().getLayer("bg"));
}
@@ -352,7 +325,7 @@ TEST(Map, StyleEarlyMutation) {
response.data = std::make_shared<std::string>(util::read_file("test/fixtures/api/water.json"));
test.fileSource.respond(Resource::Style, response);
- EXPECT_EQ(0u, test.fileSource.requests.size());
+ EXPECT_EQ(1u, test.fileSource.requests.size());
EXPECT_NE(nullptr, test.map.getStyle().getLayer("water"));
}
@@ -360,7 +333,7 @@ TEST(Map, MapLoadingSignal) {
MapTest<> test;
bool emitted = false;
- test.observer.onWillStartLoadingMapCallback = [&]() {
+ test.observer.willStartLoadingMapCallback = [&]() {
emitted = true;
};
test.map.getStyle().loadJSON(util::read_file("test/fixtures/api/empty.json"));
@@ -370,7 +343,7 @@ TEST(Map, MapLoadingSignal) {
TEST(Map, MapLoadedSignal) {
MapTest<> test { 1, MapMode::Continuous };
- test.observer.onDidFinishLoadingMapCallback = [&]() {
+ test.observer.didFinishLoadingMapCallback = [&]() {
test.runLoop.stop();
};
@@ -596,7 +569,7 @@ TEST(Map, TEST_DISABLED_ON_CI(ContinuousRendering)) {
HeadlessFrontend frontend(pixelRatio, fileSource, threadPool);
StubMapObserver observer;
- observer.didFinishRenderingFrame = [&] (MapObserver::RenderMode) {
+ observer.didFinishRenderingFrameCallback = [&] (MapObserver::RenderMode) {
// Start a timer that ends the test one second from now. If we are continuing to render
// indefinitely, the timer will be constantly restarted and never trigger. Instead, the
// emergency shutoff above will trigger, failing the test.
diff --git a/test/map/prefetch.test.cpp b/test/map/prefetch.test.cpp
index 4c82b2c965..9b61224027 100644
--- a/test/map/prefetch.test.cpp
+++ b/test/map/prefetch.test.cpp
@@ -1,5 +1,6 @@
#include <mbgl/test/util.hpp>
#include <mbgl/test/stub_file_source.hpp>
+#include <mbgl/test/stub_map_observer.hpp>
#include <mbgl/map/map.hpp>
#include <mbgl/gl/headless_frontend.hpp>
@@ -17,34 +18,38 @@
using namespace mbgl;
using namespace mbgl::style;
using namespace std::literals::string_literals;
+using namespace std::chrono_literals;
TEST(Map, PrefetchTiles) {
util::RunLoop runLoop;
ThreadPool threadPool(4);
StubFileSource fileSource;
+
+ util::Timer emergencyShutoff;
+ emergencyShutoff.start(10s, 0s, [&] {
+ runLoop.stop();
+ FAIL() << "Did not stop rendering";
+ });
+
+ StubMapObserver observer;
+ observer.didFinishLoadingMapCallback = [&] () {
+ runLoop.stop();
+ };
+
HeadlessFrontend frontend { { 512, 512 }, 1, fileSource, threadPool };
- Map map(frontend, MapObserver::nullObserver(), frontend.getSize(), 1, fileSource, threadPool, MapMode::Static);
+ Map map(frontend, observer, frontend.getSize(), 1, fileSource, threadPool, MapMode::Continuous);
std::vector<int> tiles;
fileSource.response = [&] (const Resource& res) -> optional<Response> {
- Response response;
+ static std::string tile = util::read_file("test/fixtures/map/prefetch/tile.png");
auto zoom = std::stoi(res.url);
tiles.push_back(zoom);
- // Return a red tile for prefetched tiles or green to the actual tile.
- // The end rendering result should be all green because the map is only
- // considered fully rendered when only ideal tiles are shown.
- if (zoom == int(map.getZoom()) + 1) {
- response.data = std::make_shared<std::string>(
- util::read_file("test/fixtures/map/prefetch/tile_green.png"));
- } else {
- response.data = std::make_shared<std::string>(
- util::read_file("test/fixtures/map/prefetch/tile_red.png"));
- }
-
- return { std::move(response) };
+ Response response;
+ response.data = std::make_shared<std::string>(tile);
+ return response;
};
auto checkTilesForZoom = [&](int zoom, const std::vector<int>& expected) {
@@ -53,14 +58,11 @@ TEST(Map, PrefetchTiles) {
// Force tile reloading.
map.getStyle().loadJSON(util::read_file("test/fixtures/map/prefetch/empty.json"));
map.getStyle().loadJSON(util::read_file("test/fixtures/map/prefetch/style.json"));
-
map.setLatLngZoom({ 40.726989, -73.992857 }, zoom); // Manhattan
+ runLoop.run();
- // Should always render the ideal tiles (i.e. a green map)
- test::checkImage("test/fixtures/map/prefetch", frontend.render(map));
-
+ ASSERT_EQ(tiles.size(), expected.size());
ASSERT_TRUE(std::is_permutation(tiles.begin(), tiles.end(), expected.begin()));
- ASSERT_FALSE(tiles.empty());
};
// Check defaults, should be 4.
diff --git a/test/src/mbgl/test/stub_map_observer.hpp b/test/src/mbgl/test/stub_map_observer.hpp
new file mode 100644
index 0000000000..1371577473
--- /dev/null
+++ b/test/src/mbgl/test/stub_map_observer.hpp
@@ -0,0 +1,49 @@
+#pragma once
+
+#include <mbgl/map/map_observer.hpp>
+
+#include <functional>
+
+namespace mbgl {
+
+class StubMapObserver : public MapObserver {
+public:
+ void onWillStartLoadingMap() final {
+ if (willStartLoadingMapCallback) {
+ willStartLoadingMapCallback();
+ }
+ }
+
+ void onDidFinishLoadingMap() final {
+ if (didFinishLoadingMapCallback) {
+ didFinishLoadingMapCallback();
+ }
+ }
+
+ void onDidFailLoadingMap(std::exception_ptr) final {
+ if (didFailLoadingMapCallback) {
+ didFailLoadingMapCallback();
+ }
+ }
+
+ void onDidFinishLoadingStyle() final {
+ if (didFinishLoadingStyleCallback) {
+ didFinishLoadingStyleCallback();
+ }
+ }
+
+ void onDidFinishRenderingFrame(RenderMode mode) final {
+ if (didFinishRenderingFrameCallback) {
+ didFinishRenderingFrameCallback(mode);
+ }
+ }
+
+ std::function<void()> willStartLoadingMapCallback;
+ std::function<void()> didFinishLoadingMapCallback;
+ std::function<void()> didFailLoadingMapCallback;
+ std::function<void()> didFinishLoadingStyleCallback;
+ std::function<void(RenderMode)> didFinishRenderingFrameCallback;
+};
+
+
+} // namespace mbgl
diff --git a/test/storage/offline_database.test.cpp b/test/storage/offline_database.test.cpp
index 94daf59c02..620e6eaa6d 100644
--- a/test/storage/offline_database.test.cpp
+++ b/test/storage/offline_database.test.cpp
@@ -38,6 +38,10 @@ void writeFile(const char* name, const std::string& data) {
mbgl::util::write_file(name, data);
}
+void copyFile(const char* orig, const char* dest) {
+ mbgl::util::write_file(dest, mbgl::util::read_file(orig));
+}
+
} // namespace
TEST(OfflineDatabase, TEST_REQUIRES_WRITE(Create)) {
@@ -62,7 +66,7 @@ TEST(OfflineDatabase, TEST_REQUIRES_WRITE(SchemaVersion)) {
std::string path("test/fixtures/offline_database/offline.db");
{
- mapbox::sqlite::Database db(path, mapbox::sqlite::Create | mapbox::sqlite::ReadWrite);
+ mapbox::sqlite::Database db{ path, mapbox::sqlite::Create | mapbox::sqlite::ReadWrite };
db.exec("PRAGMA user_version = 1");
}
@@ -143,6 +147,36 @@ TEST(OfflineDatabase, PutResource) {
EXPECT_EQ("second", *updateGetResult->data);
}
+TEST(OfflineDatabase, TEST_REQUIRES_WRITE(GetResourceFromOfflineRegion)) {
+ using namespace mbgl;
+
+ createDir("test/fixtures/offline_database");
+ deleteFile("test/fixtures/offline_database/satellite.db");
+ copyFile("test/fixtures/offline_database/satellite_test.db", "test/fixtures/offline_database/satellite.db");
+
+ OfflineDatabase db("test/fixtures/offline_database/satellite.db", mapbox::sqlite::ReadOnly);
+
+ Resource resource = Resource::style("mapbox://styles/mapbox/satellite-v9");
+ ASSERT_TRUE(db.get(resource));
+}
+
+TEST(OfflineDatabase, PutAndGetResource) {
+ using namespace mbgl;
+
+ OfflineDatabase db(":memory:");
+
+ Response response1;
+ response1.data = std::make_shared<std::string>("foobar");
+
+ Resource resource = Resource::style("mapbox://example.com/style");
+
+ db.put(resource, response1);
+
+ auto response2 = db.get(resource);
+
+ ASSERT_EQ(*response1.data, *(*response2).data);
+}
+
TEST(OfflineDatabase, PutTile) {
using namespace mbgl;
@@ -565,40 +599,45 @@ TEST(OfflineDatabase, OfflineMapboxTileCount) {
}
static int databasePageCount(const std::string& path) {
- mapbox::sqlite::Database db(path, mapbox::sqlite::ReadOnly);
- mapbox::sqlite::Statement stmt = db.prepare("pragma page_count");
- stmt.run();
- return stmt.get<int>(0);
+ mapbox::sqlite::Database db{ path, mapbox::sqlite::ReadOnly };
+ mapbox::sqlite::Statement stmt{ db, "pragma page_count" };
+ mapbox::sqlite::Query query{ stmt };
+ query.run();
+ return query.get<int>(0);
}
static int databaseUserVersion(const std::string& path) {
- mapbox::sqlite::Database db(path, mapbox::sqlite::ReadOnly);
- mapbox::sqlite::Statement stmt = db.prepare("pragma user_version");
- stmt.run();
- return stmt.get<int>(0);
+ mapbox::sqlite::Database db{ path, mapbox::sqlite::ReadOnly };
+ mapbox::sqlite::Statement stmt{ db, "pragma user_version" };
+ mapbox::sqlite::Query query{ stmt };
+ query.run();
+ return query.get<int>(0);
}
static std::string databaseJournalMode(const std::string& path) {
- mapbox::sqlite::Database db(path, mapbox::sqlite::ReadOnly);
- mapbox::sqlite::Statement stmt = db.prepare("pragma journal_mode");
- stmt.run();
- return stmt.get<std::string>(0);
+ mapbox::sqlite::Database db{ path, mapbox::sqlite::ReadOnly };
+ mapbox::sqlite::Statement stmt{ db, "pragma journal_mode" };
+ mapbox::sqlite::Query query{ stmt };
+ query.run();
+ return query.get<std::string>(0);
}
static int databaseSyncMode(const std::string& path) {
- mapbox::sqlite::Database db(path, mapbox::sqlite::ReadOnly);
- mapbox::sqlite::Statement stmt = db.prepare("pragma synchronous");
- stmt.run();
- return stmt.get<int>(0);
+ mapbox::sqlite::Database db{ path, mapbox::sqlite::ReadOnly };
+ mapbox::sqlite::Statement stmt{ db, "pragma synchronous" };
+ mapbox::sqlite::Query query{ stmt };
+ query.run();
+ return query.get<int>(0);
}
static std::vector<std::string> databaseTableColumns(const std::string& path, const std::string& name) {
- mapbox::sqlite::Database db(path, mapbox::sqlite::ReadOnly);
+ mapbox::sqlite::Database db{ path, mapbox::sqlite::ReadOnly };
const auto sql = std::string("pragma table_info(") + name + ")";
- mapbox::sqlite::Statement stmt = db.prepare(sql.c_str());
+ mapbox::sqlite::Statement stmt{ db, sql.c_str() };
+ mapbox::sqlite::Query query{ stmt };
std::vector<std::string> columns;
- while (stmt.run()) {
- columns.push_back(stmt.get<std::string>(1));
+ while (query.run()) {
+ columns.push_back(query.get<std::string>(1));
}
return columns;
}
diff --git a/test/storage/sqlite.test.cpp b/test/storage/sqlite.test.cpp
index 36715a2fd0..918200181f 100644
--- a/test/storage/sqlite.test.cpp
+++ b/test/storage/sqlite.test.cpp
@@ -9,21 +9,23 @@ TEST(SQLite, Statement) {
mapbox::sqlite::Database db(":memory:", mapbox::sqlite::Create | mapbox::sqlite::ReadWrite);
db.exec("CREATE TABLE test (id INTEGER);");
- mapbox::sqlite::Statement stmt1 = db.prepare("INSERT INTO test (id) VALUES (?1);");
- ASSERT_EQ(stmt1.lastInsertRowId(), 0);
- ASSERT_EQ(stmt1.changes(), 0u);
- stmt1.bind(1, 10);
- stmt1.run();
- ASSERT_EQ(stmt1.lastInsertRowId(), 1);
- ASSERT_EQ(stmt1.changes(), 1u);
+ mapbox::sqlite::Statement stmt1{ db, "INSERT INTO test (id) VALUES (?1);" };
+ mapbox::sqlite::Query query1{ stmt1 };
+ ASSERT_EQ(query1.lastInsertRowId(), 0);
+ ASSERT_EQ(query1.changes(), 0u);
+ query1.bind(1, 10);
+ query1.run();
+ ASSERT_EQ(query1.lastInsertRowId(), 1);
+ ASSERT_EQ(query1.changes(), 1u);
- mapbox::sqlite::Statement stmt2 = db.prepare("INSERT INTO test (id) VALUES (?1);");
- ASSERT_EQ(stmt2.lastInsertRowId(), 0);
- ASSERT_EQ(stmt2.changes(), 0u);
- stmt2.bind(1, 20);
- stmt2.run();
- ASSERT_EQ(stmt2.lastInsertRowId(), 2);
- ASSERT_EQ(stmt2.changes(), 1u);
+ mapbox::sqlite::Statement stmt2{ db, "INSERT INTO test (id) VALUES (?1);" };
+ mapbox::sqlite::Query query2{ stmt2 };
+ ASSERT_EQ(query2.lastInsertRowId(), 0);
+ ASSERT_EQ(query2.changes(), 0u);
+ query2.bind(1, 20);
+ query2.run();
+ ASSERT_EQ(query2.lastInsertRowId(), 2);
+ ASSERT_EQ(query2.changes(), 1u);
}
TEST(SQLite, TEST_REQUIRES_WRITE(CantOpenException)) {
@@ -33,6 +35,6 @@ TEST(SQLite, TEST_REQUIRES_WRITE(CantOpenException)) {
mapbox::sqlite::Database("test/fixtures/offline_database/foobar123.db", mapbox::sqlite::ReadOnly);
FAIL();
} catch (mapbox::sqlite::Exception& ex) {
- ASSERT_EQ(ex.code, mapbox::sqlite::Exception::Code::CANTOPEN);
+ ASSERT_EQ(ex.code, mapbox::sqlite::ResultCode::CantOpen);
}
}
diff --git a/test/style/conversion/tileset.test.cpp b/test/style/conversion/tileset.test.cpp
index 8002cd038f..9487277cca 100644
--- a/test/style/conversion/tileset.test.cpp
+++ b/test/style/conversion/tileset.test.cpp
@@ -52,6 +52,16 @@ TEST(Tileset, InvalidBounds) {
}
}
+TEST(Tileset, ValidWorldBounds) {
+ Error error;
+ mbgl::optional<Tileset> converted = convertJSON<Tileset>(R"JSON({
+ "tiles": ["http://mytiles"],
+ "bounds": [-180, -90, 180, 90]
+ })JSON", error);
+ EXPECT_TRUE((bool) converted);
+ EXPECT_EQ(converted->bounds, LatLngBounds::hull({90, -180}, {-90, 180}));
+}
+
TEST(Tileset, FullConversion) {
Error error;
Tileset converted = *convertJSON<Tileset>(R"JSON({
diff --git a/test/tile/raster_dem_tile.test.cpp b/test/tile/raster_dem_tile.test.cpp
index c6f9b80b42..5a6f5a8c9a 100644
--- a/test/tile/raster_dem_tile.test.cpp
+++ b/test/tile/raster_dem_tile.test.cpp
@@ -62,7 +62,7 @@ TEST(RasterDEMTile, onError) {
TEST(RasterDEMTile, onParsed) {
RasterDEMTileTest test;
RasterDEMTile tile(OverscaledTileID(0, 0, 0), test.tileParameters, test.tileset);
- tile.onParsed(std::make_unique<HillshadeBucket>(PremultipliedImage({16, 16})), 0);
+ tile.onParsed(std::make_unique<HillshadeBucket>(PremultipliedImage({16, 16}), Tileset::DEMEncoding::Mapbox), 0);
EXPECT_TRUE(tile.isRenderable());
EXPECT_TRUE(tile.isLoaded());
EXPECT_TRUE(tile.isComplete());
diff --git a/test/util/run_loop.test.cpp b/test/util/run_loop.test.cpp
index 57bc613f9e..4d2c704421 100644
--- a/test/util/run_loop.test.cpp
+++ b/test/util/run_loop.test.cpp
@@ -50,3 +50,17 @@ TEST(RunLoop, MultipleRun) {
EXPECT_TRUE(secondTimeout);
}
+
+TEST(RunLoop, Priorities) {
+ std::vector<int> order;
+
+ RunLoop loop(RunLoop::Type::New);
+ loop.invoke([&] { order.push_back(1); });
+ loop.invoke(RunLoop::Priority::High, [&] { order.push_back(2); });
+ loop.invoke([&] { order.push_back(3); });
+ loop.invoke(RunLoop::Priority::High, [&] { order.push_back(4); });
+ loop.invoke(RunLoop::Priority::Default, [&] { loop.stop(); });
+ loop.run();
+
+ EXPECT_EQ((std::vector<int>{ 2, 4, 1, 3 }), order);
+}
diff --git a/test/util/tile_range.test.cpp b/test/util/tile_range.test.cpp
index dc8ae28705..c4c37c74d7 100644
--- a/test/util/tile_range.test.cpp
+++ b/test/util/tile_range.test.cpp
@@ -1,4 +1,3 @@
-
#include <mbgl/util/tile_range.hpp>
#include <mbgl/util/geo.hpp>
#include <mbgl/map/transform.hpp>
@@ -25,6 +24,18 @@ TEST(TileRange, ContainsBoundsFromTile) {
EXPECT_TRUE(range.contains(CanonicalTileID(10, 162, 395)));
}
}
+
+TEST(TileRange, ContainsMultiZoom) {
+ auto wrappedBounds = LatLngBounds::hull({ 37.6609, -122.5744 }, { 37.8271, -122.3204 });
+ auto range = util::TileRange::fromLatLngBounds(wrappedBounds, 5, 13);
+ EXPECT_FALSE(range.contains(CanonicalTileID(0, 0, 0)));
+ EXPECT_FALSE(range.contains(CanonicalTileID(5, 3, 11)));
+ EXPECT_FALSE(range.contains(CanonicalTileID(6, 9, 22)));
+ EXPECT_TRUE(range.contains(CanonicalTileID(5, 5, 12)));
+ EXPECT_TRUE(range.contains(CanonicalTileID(6, 10, 24)));
+ EXPECT_TRUE(range.contains(CanonicalTileID(13, 1310, 3166)));
+}
+
TEST(TileRange, ContainsIntersectingTiles) {
auto bounds = LatLngBounds::hull({ 37.6609, -122.5744 }, { 37.8271, -122.3204 });
auto range = util::TileRange::fromLatLngBounds(bounds, 13);