summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.ycm_extra_conf.py2
-rw-r--r--INSTALL.md2
-rw-r--r--benchmark/function/camera_function.benchmark.cpp18
-rw-r--r--benchmark/function/composite_function.benchmark.cpp17
-rw-r--r--benchmark/function/source_function.benchmark.cpp17
-rw-r--r--benchmark/parse/filter.benchmark.cpp8
-rw-r--r--cmake/benchmark.cmake1
-rw-r--r--cmake/core-files.cmake66
-rw-r--r--cmake/node.cmake14
-rw-r--r--cmake/render.cmake1
-rw-r--r--cmake/test-files.cmake6
-rw-r--r--include/mbgl/map/map.hpp8
-rw-r--r--include/mbgl/map/mode.hpp9
-rw-r--r--include/mbgl/renderer/mode.hpp18
-rw-r--r--include/mbgl/renderer/renderer.hpp2
-rw-r--r--include/mbgl/storage/offline.hpp6
-rw-r--r--include/mbgl/style/conversion.hpp254
-rw-r--r--include/mbgl/style/conversion/constant.hpp98
-rw-r--r--include/mbgl/style/conversion/coordinate.hpp21
-rw-r--r--include/mbgl/style/conversion/data_driven_property_value.hpp28
-rw-r--r--include/mbgl/style/conversion/expression.hpp39
-rw-r--r--include/mbgl/style/conversion/filter.hpp243
-rw-r--r--include/mbgl/style/conversion/function.hpp47
-rw-r--r--include/mbgl/style/conversion/geojson.hpp10
-rw-r--r--include/mbgl/style/conversion/geojson_options.hpp79
-rw-r--r--include/mbgl/style/conversion/get_json_type.hpp14
-rw-r--r--include/mbgl/style/conversion/layer.hpp210
-rw-r--r--include/mbgl/style/conversion/light.hpp106
-rw-r--r--include/mbgl/style/conversion/make_property_setters.hpp211
-rw-r--r--include/mbgl/style/conversion/make_property_setters.hpp.ejs48
-rw-r--r--include/mbgl/style/conversion/position.hpp14
-rw-r--r--include/mbgl/style/conversion/property_value.hpp19
-rw-r--r--include/mbgl/style/conversion/source.hpp176
-rw-r--r--include/mbgl/style/conversion/tileset.hpp65
-rw-r--r--include/mbgl/style/conversion/transition_options.hpp32
-rw-r--r--include/mbgl/style/expression/array_assertion.hpp39
-rw-r--r--include/mbgl/style/expression/assertion.hpp33
-rw-r--r--include/mbgl/style/expression/at.hpp38
-rw-r--r--include/mbgl/style/expression/boolean_operator.hpp49
-rw-r--r--include/mbgl/style/expression/case.hpp36
-rw-r--r--include/mbgl/style/expression/check_subtype.hpp17
-rw-r--r--include/mbgl/style/expression/coalesce.hpp45
-rw-r--r--include/mbgl/style/expression/coercion.hpp34
-rw-r--r--include/mbgl/style/expression/compound_expression.hpp138
-rw-r--r--include/mbgl/style/expression/expression.hpp169
-rw-r--r--include/mbgl/style/expression/find_zoom_curve.hpp20
-rw-r--r--include/mbgl/style/expression/get_covering_stops.hpp18
-rw-r--r--include/mbgl/style/expression/interpolate.hpp177
-rw-r--r--include/mbgl/style/expression/is_constant.hpp35
-rw-r--r--include/mbgl/style/expression/is_expression.hpp13
-rw-r--r--include/mbgl/style/expression/let.hpp72
-rw-r--r--include/mbgl/style/expression/literal.hpp38
-rw-r--r--include/mbgl/style/expression/match.hpp45
-rw-r--r--include/mbgl/style/expression/parsing_context.hpp147
-rw-r--r--include/mbgl/style/expression/step.hpp45
-rw-r--r--include/mbgl/style/expression/type.hpp111
-rw-r--r--include/mbgl/style/expression/value.hpp153
-rw-r--r--include/mbgl/style/function/camera_function.hpp58
-rw-r--r--include/mbgl/style/function/composite_function.hpp134
-rw-r--r--include/mbgl/style/function/convert.hpp351
-rw-r--r--include/mbgl/style/function/source_function.hpp39
-rw-r--r--include/mbgl/style/types.hpp6
-rw-r--r--include/mbgl/util/enum.hpp1
-rw-r--r--include/mbgl/util/geo.hpp7
-rw-r--r--include/mbgl/util/interpolate.hpp33
-rw-r--r--include/mbgl/util/projection.hpp4
-rw-r--r--include/mbgl/util/tileset.hpp1
-rw-r--r--include/mbgl/util/unitbezier.hpp6
m---------mapbox-gl-js0
-rw-r--r--package.json10
-rw-r--r--platform/android/CHANGELOG.md35
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java3
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/snapshot/MapSnapshotterMarkerActivity.java4
-rw-r--r--platform/android/README.md84
-rw-r--r--platform/android/config.cmake6
-rw-r--r--platform/android/mbgl/gl/gl_impl.hpp5
-rw-r--r--platform/android/src/map_renderer.cpp10
-rw-r--r--platform/android/src/map_renderer_runnable.cpp10
-rw-r--r--platform/android/src/map_renderer_runnable.hpp7
-rw-r--r--platform/android/src/style/android_conversion.hpp147
-rw-r--r--platform/android/src/style/conversion/filter.hpp3
-rw-r--r--platform/android/src/style/conversion/geojson.hpp24
-rw-r--r--platform/android/src/style/conversion/url_or_tileset.hpp9
-rw-r--r--platform/android/src/style/layers/layer.cpp21
-rw-r--r--platform/android/src/style/sources/geojson_source.cpp10
-rw-r--r--platform/android/src/style/value.cpp2
-rw-r--r--platform/android/src/style/value.hpp8
-rw-r--r--platform/darwin/mbgl/gl/gl_impl.hpp16
-rw-r--r--platform/darwin/src/MGLConversion.h223
-rw-r--r--platform/darwin/src/MGLStyleValue_Private.h4
-rw-r--r--platform/default/mbgl/gl/headless_frontend.cpp8
-rw-r--r--platform/default/mbgl/gl/headless_frontend.hpp5
-rw-r--r--platform/default/mbgl/storage/offline.cpp6
-rw-r--r--platform/default/mbgl/storage/offline_download.hpp2
-rw-r--r--platform/glfw/settings_json.hpp3
-rw-r--r--platform/ios/CHANGELOG.md6
-rw-r--r--platform/ios/INSTALL.md4
-rw-r--r--platform/ios/README.md7
-rw-r--r--platform/ios/src/MGLMapView.mm1
-rw-r--r--platform/linux/config.cmake5
-rw-r--r--platform/linux/mbgl/gl/gl_impl.hpp11
-rw-r--r--platform/macos/CHANGELOG.md3
-rw-r--r--platform/macos/config.cmake4
-rw-r--r--platform/node/CHANGELOG.md8
-rw-r--r--platform/node/src/node_conversion.hpp183
-rw-r--r--platform/node/src/node_expression.cpp230
-rw-r--r--platform/node/src/node_expression.hpp40
-rw-r--r--platform/node/src/node_geojson.hpp17
-rw-r--r--platform/node/src/node_map.cpp97
-rw-r--r--platform/node/src/node_map.hpp3
-rw-r--r--platform/node/src/node_mapbox_gl_native.cpp2
-rw-r--r--platform/node/test/expression.test.js71
-rw-r--r--platform/node/test/ignores.json56
-rw-r--r--platform/node/test/js/map.test.js3
-rw-r--r--platform/node/test/suite_implementation.js6
-rw-r--r--platform/qt/config.cmake5
-rw-r--r--platform/qt/include/qmapboxgl.hpp6
-rw-r--r--platform/qt/mbgl/gl/gl_impl.hpp173
-rw-r--r--platform/qt/qt.cmake23
-rw-r--r--platform/qt/qt5.cmake6
-rw-r--r--platform/qt/src/http_file_source.cpp3
-rw-r--r--platform/qt/src/http_request.cpp7
-rw-r--r--platform/qt/src/http_request.hpp2
-rw-r--r--platform/qt/src/qmapboxgl.cpp46
-rw-r--r--platform/qt/src/qmapboxgl_p.hpp7
-rw-r--r--platform/qt/src/qt_conversion.hpp191
-rw-r--r--platform/qt/src/qt_geojson.cpp166
-rw-r--r--platform/qt/src/qt_geojson.hpp194
-rw-r--r--platform/qt/src/sqlite3.cpp74
-rw-r--r--platform/qt/test/qmapboxgl.test.cpp5
-rw-r--r--scripts/generate-style-code.js4
-rw-r--r--src/mbgl/annotation/render_annotation_source.hpp2
-rw-r--r--src/mbgl/gl/context.cpp6
-rw-r--r--src/mbgl/gl/gl.hpp30
-rw-r--r--src/mbgl/map/map.cpp29
-rw-r--r--src/mbgl/map/transform.cpp26
-rw-r--r--src/mbgl/map/transform.hpp8
-rw-r--r--src/mbgl/map/transform_state.cpp9
-rw-r--r--src/mbgl/map/transform_state.hpp3
-rw-r--r--src/mbgl/programs/symbol_program.hpp51
-rw-r--r--src/mbgl/renderer/paint_parameters.hpp1
-rw-r--r--src/mbgl/renderer/paint_property_binder.hpp11
-rw-r--r--src/mbgl/renderer/renderer_impl.cpp18
-rw-r--r--src/mbgl/renderer/renderer_impl.hpp2
-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_source.hpp2
-rw-r--r--src/mbgl/renderer/sources/render_vector_source.hpp2
-rw-r--r--src/mbgl/renderer/tile_pyramid.hpp2
-rw-r--r--src/mbgl/style/conversion/constant.cpp94
-rw-r--r--src/mbgl/style/conversion/coordinate.cpp29
-rw-r--r--src/mbgl/style/conversion/filter.cpp248
-rw-r--r--src/mbgl/style/conversion/geojson.cpp18
-rw-r--r--src/mbgl/style/conversion/geojson_options.cpp85
-rw-r--r--src/mbgl/style/conversion/get_json_type.cpp34
-rw-r--r--src/mbgl/style/conversion/json.hpp2
-rw-r--r--src/mbgl/style/conversion/layer.cpp206
-rw-r--r--src/mbgl/style/conversion/light.cpp115
-rw-r--r--src/mbgl/style/conversion/make_property_setters.hpp209
-rw-r--r--src/mbgl/style/conversion/make_property_setters.hpp.ejs46
-rw-r--r--src/mbgl/style/conversion/position.cpp22
-rw-r--r--src/mbgl/style/conversion/property_setter.hpp (renamed from include/mbgl/style/conversion/property_setter.hpp)14
-rw-r--r--src/mbgl/style/conversion/source.cpp175
-rw-r--r--src/mbgl/style/conversion/tileset.cpp73
-rw-r--r--src/mbgl/style/conversion/transition_options.cpp40
-rw-r--r--src/mbgl/style/expression/array_assertion.cpp85
-rw-r--r--src/mbgl/style/expression/assertion.cpp73
-rw-r--r--src/mbgl/style/expression/at.cpp63
-rw-r--r--src/mbgl/style/expression/boolean_operator.cpp87
-rw-r--r--src/mbgl/style/expression/case.cpp90
-rw-r--r--src/mbgl/style/expression/check_subtype.cpp60
-rw-r--r--src/mbgl/style/expression/coalesce.cpp62
-rw-r--r--src/mbgl/style/expression/coercion.cpp143
-rw-r--r--src/mbgl/style/expression/compound_expression.cpp571
-rw-r--r--src/mbgl/style/expression/find_zoom_curve.cpp76
-rw-r--r--src/mbgl/style/expression/get_covering_stops.cpp26
-rw-r--r--src/mbgl/style/expression/interpolate.cpp211
-rw-r--r--src/mbgl/style/expression/is_constant.cpp40
-rw-r--r--src/mbgl/style/expression/is_expression.cpp29
-rw-r--r--src/mbgl/style/expression/let.cpp91
-rw-r--r--src/mbgl/style/expression/literal.cpp108
-rw-r--r--src/mbgl/style/expression/match.cpp262
-rw-r--r--src/mbgl/style/expression/parsing_context.cpp206
-rw-r--r--src/mbgl/style/expression/step.cpp151
-rw-r--r--src/mbgl/style/expression/util.cpp39
-rw-r--r--src/mbgl/style/expression/util.hpp14
-rw-r--r--src/mbgl/style/expression/value.cpp322
-rw-r--r--src/mbgl/style/function/expression.cpp38
-rw-r--r--src/mbgl/style/parser.cpp6
-rw-r--r--src/mbgl/style/rapidjson_conversion.hpp152
-rw-r--r--src/mbgl/text/glyph_manager.cpp11
-rw-r--r--src/mbgl/text/glyph_manager.hpp2
-rw-r--r--src/mbgl/util/mapbox.cpp6
-rw-r--r--src/mbgl/util/mapbox.hpp4
-rw-r--r--src/mbgl/util/tile_cover.cpp8
-rw-r--r--src/mbgl/util/tile_cover.hpp2
-rw-r--r--test/fixtures/expression_equality/acos.a.json4
-rw-r--r--test/fixtures/expression_equality/acos.b.json4
-rw-r--r--test/fixtures/expression_equality/all.a.json17
-rw-r--r--test/fixtures/expression_equality/all.b.json17
-rw-r--r--test/fixtures/expression_equality/any.a.json17
-rw-r--r--test/fixtures/expression_equality/any.b.json17
-rw-r--r--test/fixtures/expression_equality/array.a.json11
-rw-r--r--test/fixtures/expression_equality/array.b.json11
-rw-r--r--test/fixtures/expression_equality/asin.a.json4
-rw-r--r--test/fixtures/expression_equality/asin.b.json4
-rw-r--r--test/fixtures/expression_equality/at.a.json20
-rw-r--r--test/fixtures/expression_equality/at.b.json20
-rw-r--r--test/fixtures/expression_equality/atan.a.json4
-rw-r--r--test/fixtures/expression_equality/atan.b.json4
-rw-r--r--test/fixtures/expression_equality/boolean.a.json7
-rw-r--r--test/fixtures/expression_equality/boolean.b.json7
-rw-r--r--test/fixtures/expression_equality/case.a.json14
-rw-r--r--test/fixtures/expression_equality/case.b.json14
-rw-r--r--test/fixtures/expression_equality/coalesce.a.json16
-rw-r--r--test/fixtures/expression_equality/coalesce.b.json16
-rw-r--r--test/fixtures/expression_equality/concat.a.json6
-rw-r--r--test/fixtures/expression_equality/concat.b.json6
-rw-r--r--test/fixtures/expression_equality/cos.a.json4
-rw-r--r--test/fixtures/expression_equality/cos.b.json4
-rw-r--r--test/fixtures/expression_equality/divide.a.json5
-rw-r--r--test/fixtures/expression_equality/divide.b.json5
-rw-r--r--test/fixtures/expression_equality/downcase.a.json4
-rw-r--r--test/fixtures/expression_equality/downcase.b.json4
-rw-r--r--test/fixtures/expression_equality/get.a.json7
-rw-r--r--test/fixtures/expression_equality/get.b.json7
-rw-r--r--test/fixtures/expression_equality/has.a.json4
-rw-r--r--test/fixtures/expression_equality/has.b.json4
-rw-r--r--test/fixtures/expression_equality/heatmap-density.a.json23
-rw-r--r--test/fixtures/expression_equality/heatmap-density.b.json23
-rw-r--r--test/fixtures/expression_equality/let.a.json25
-rw-r--r--test/fixtures/expression_equality/let.b.json25
-rw-r--r--test/fixtures/expression_equality/ln.a.json4
-rw-r--r--test/fixtures/expression_equality/ln.b.json6
-rw-r--r--test/fixtures/expression_equality/log10.a.json4
-rw-r--r--test/fixtures/expression_equality/log10.b.json4
-rw-r--r--test/fixtures/expression_equality/log2.a.json4
-rw-r--r--test/fixtures/expression_equality/log2.b.json4
-rw-r--r--test/fixtures/expression_equality/match.a.json12
-rw-r--r--test/fixtures/expression_equality/match.b.json12
-rw-r--r--test/fixtures/expression_equality/max.a.json6
-rw-r--r--test/fixtures/expression_equality/max.b.json6
-rw-r--r--test/fixtures/expression_equality/min.a.json5
-rw-r--r--test/fixtures/expression_equality/min.b.json5
-rw-r--r--test/fixtures/expression_equality/minus.a.json5
-rw-r--r--test/fixtures/expression_equality/minus.b.json5
-rw-r--r--test/fixtures/expression_equality/mod.a.json5
-rw-r--r--test/fixtures/expression_equality/mod.b.json5
-rw-r--r--test/fixtures/expression_equality/not.a.json10
-rw-r--r--test/fixtures/expression_equality/not.b.json10
-rw-r--r--test/fixtures/expression_equality/number.a.json7
-rw-r--r--test/fixtures/expression_equality/number.b.json7
-rw-r--r--test/fixtures/expression_equality/object.a.json7
-rw-r--r--test/fixtures/expression_equality/object.b.json7
-rw-r--r--test/fixtures/expression_equality/plus.a.json7
-rw-r--r--test/fixtures/expression_equality/plus.b.json7
-rw-r--r--test/fixtures/expression_equality/pow.a.json11
-rw-r--r--test/fixtures/expression_equality/pow.b.json11
-rw-r--r--test/fixtures/expression_equality/rgb.a.json6
-rw-r--r--test/fixtures/expression_equality/rgb.b.json6
-rw-r--r--test/fixtures/expression_equality/rgba.a.json7
-rw-r--r--test/fixtures/expression_equality/rgba.b.json7
-rw-r--r--test/fixtures/expression_equality/sin.a.json4
-rw-r--r--test/fixtures/expression_equality/sin.b.json4
-rw-r--r--test/fixtures/expression_equality/sqrt.a.json7
-rw-r--r--test/fixtures/expression_equality/sqrt.b.json7
-rw-r--r--test/fixtures/expression_equality/step.a.json18
-rw-r--r--test/fixtures/expression_equality/step.b.json18
-rw-r--r--test/fixtures/expression_equality/string.a.json7
-rw-r--r--test/fixtures/expression_equality/string.b.json7
-rw-r--r--test/fixtures/expression_equality/tan.a.json4
-rw-r--r--test/fixtures/expression_equality/tan.b.json4
-rw-r--r--test/fixtures/expression_equality/times.a.json7
-rw-r--r--test/fixtures/expression_equality/times.b.json7
-rw-r--r--test/fixtures/expression_equality/to-boolean.a.json7
-rw-r--r--test/fixtures/expression_equality/to-boolean.b.json7
-rw-r--r--test/fixtures/expression_equality/to-color.a.json7
-rw-r--r--test/fixtures/expression_equality/to-color.b.json7
-rw-r--r--test/fixtures/expression_equality/to-number.a.json7
-rw-r--r--test/fixtures/expression_equality/to-number.b.json7
-rw-r--r--test/fixtures/expression_equality/to-string.a.json7
-rw-r--r--test/fixtures/expression_equality/to-string.b.json7
-rw-r--r--test/fixtures/expression_equality/typeof.a.json7
-rw-r--r--test/fixtures/expression_equality/typeof.b.json7
-rw-r--r--test/fixtures/expression_equality/upcase.a.json4
-rw-r--r--test/fixtures/expression_equality/upcase.b.json4
-rw-r--r--test/fixtures/expression_equality/zoom.a.json13
-rw-r--r--test/fixtures/expression_equality/zoom.b.json13
-rw-r--r--test/fixtures/shared_context/expected.pngbin0 -> 6109 bytes
-rw-r--r--test/fixtures/style_parser/expressions.info.json12
-rw-r--r--test/fixtures/style_parser/expressions.style.json74
-rw-r--r--test/gl/bucket.test.cpp17
-rw-r--r--test/gl/context.test.cpp114
-rw-r--r--test/src/mbgl/test/conversion_stubs.hpp124
-rw-r--r--test/src/mbgl/test/stub_file_source.cpp16
-rw-r--r--test/src/mbgl/test/stub_file_source.hpp8
-rw-r--r--test/storage/offline.test.cpp1
-rw-r--r--test/style/conversion/function.test.cpp36
-rw-r--r--test/style/conversion/geojson_options.test.cpp39
-rw-r--r--test/style/conversion/layer.test.cpp8
-rw-r--r--test/style/conversion/light.test.cpp7
-rw-r--r--test/style/expression/expression.test.cpp91
-rw-r--r--test/style/expression/util.test.cpp23
-rw-r--r--test/style/filter.test.cpp9
-rw-r--r--test/style/source.test.cpp1
-rw-r--r--test/text/glyph_loader.test.cpp49
-rw-r--r--test/util/geo.test.cpp6
-rw-r--r--test/util/mapbox.test.cpp1
308 files changed, 9826 insertions, 2603 deletions
diff --git a/.ycm_extra_conf.py b/.ycm_extra_conf.py
index e395609548..63616a1e98 100644
--- a/.ycm_extra_conf.py
+++ b/.ycm_extra_conf.py
@@ -38,6 +38,8 @@ import ycm_core
compilation_database_folders = [
'build/linux-x86_64/Debug',
+ 'build/qt-linux-x86_64/Debug',
+ 'build/qt4-linux-x86_64/Debug',
'build/macos/compdb/Debug',
]
diff --git a/INSTALL.md b/INSTALL.md
index 61d2271de5..d39c3ad62e 100644
--- a/INSTALL.md
+++ b/INSTALL.md
@@ -2,7 +2,7 @@
**Just trying to use Mapbox GL Native? You don't need to read this stuff! We
provide [easy-to-install, prebuilt versions of the Mapbox SDKs for iOS and Android
-that you can download instantly and get started with fast](https://www.mapbox.com/mobile/).**
+that you can download instantly and get started with fast](https://www.mapbox.com/install/).**
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.
diff --git a/benchmark/function/camera_function.benchmark.cpp b/benchmark/function/camera_function.benchmark.cpp
index 1f8fe4579f..26de5701db 100644
--- a/benchmark/function/camera_function.benchmark.cpp
+++ b/benchmark/function/camera_function.benchmark.cpp
@@ -1,20 +1,14 @@
#include <benchmark/benchmark.h>
#include <mbgl/style/function/source_function.hpp>
-
-#include <mbgl/style/rapidjson_conversion.hpp>
#include <mbgl/style/conversion.hpp>
+#include <mbgl/style/conversion/json.hpp>
#include <mbgl/style/conversion/function.hpp>
-#include <rapidjson/document.h>
-
-
using namespace mbgl;
using namespace mbgl::style;
-static rapidjson::GenericDocument<rapidjson::UTF8<>, rapidjson::CrtAllocator> createFunctionJSON(size_t stopCount) {
- rapidjson::GenericDocument<rapidjson::UTF8<>, rapidjson::CrtAllocator> doc;
-
+static std::string createFunctionJSON(size_t stopCount) {
std::string stops = "[";
for (size_t i = 0; i < stopCount; i++) {
std::string value = std::to_string(24.0f / stopCount * i);
@@ -22,9 +16,7 @@ static rapidjson::GenericDocument<rapidjson::UTF8<>, rapidjson::CrtAllocator> cr
stops += "[" + value + ", " + value + "]";
}
stops += "]";
-
- doc.Parse<0>(R"({"type": "exponential", "base": 2, "stops": )" + stops + "}");
- return doc;
+ return R"({"type": "exponential", "base": 2, "stops": )" + stops + "}";
}
static void Parse_CameraFunction(benchmark::State& state) {
@@ -35,7 +27,7 @@ static void Parse_CameraFunction(benchmark::State& state) {
state.PauseTiming();
auto doc = createFunctionJSON(stopCount);
state.ResumeTiming();
- optional<CameraFunction<float>> result = conversion::convert<CameraFunction<float>, JSValue>(doc, error);
+ optional<CameraFunction<float>> result = conversion::convertJSON<CameraFunction<float>>(doc, error);
if (!result) {
state.SkipWithError(error.message.c_str());
}
@@ -47,7 +39,7 @@ static void Evaluate_CameraFunction(benchmark::State& state) {
size_t stopCount = state.range(0);
auto doc = createFunctionJSON(stopCount);
conversion::Error error;
- optional<CameraFunction<float>> function = conversion::convert<CameraFunction<float>, JSValue>(doc, error);
+ optional<CameraFunction<float>> function = conversion::convertJSON<CameraFunction<float>>(doc, error);
if (!function) {
state.SkipWithError(error.message.c_str());
}
diff --git a/benchmark/function/composite_function.benchmark.cpp b/benchmark/function/composite_function.benchmark.cpp
index f04b6c7073..e2545e6349 100644
--- a/benchmark/function/composite_function.benchmark.cpp
+++ b/benchmark/function/composite_function.benchmark.cpp
@@ -5,19 +5,14 @@
#include <mbgl/style/function/composite_exponential_stops.hpp>
#include <mbgl/style/function/composite_function.hpp>
-#include <mbgl/style/rapidjson_conversion.hpp>
#include <mbgl/style/conversion.hpp>
+#include <mbgl/style/conversion/json.hpp>
#include <mbgl/style/conversion/function.hpp>
-#include <rapidjson/document.h>
-
-
using namespace mbgl;
using namespace mbgl::style;
-static rapidjson::GenericDocument<rapidjson::UTF8<>, rapidjson::CrtAllocator> createFunctionJSON(size_t stopCount) {
- rapidjson::GenericDocument<rapidjson::UTF8<>, rapidjson::CrtAllocator> doc;
-
+static std::string createFunctionJSON(size_t stopCount) {
std::string stops = "[";
for (size_t outerStop = 0; outerStop < stopCount; outerStop++) {
for (size_t innerStop = 0; innerStop < stopCount; innerStop++) {
@@ -29,9 +24,7 @@ static rapidjson::GenericDocument<rapidjson::UTF8<>, rapidjson::CrtAllocator> cr
}
}
stops += "]";
-
- doc.Parse<0>(R"({"type": "exponential", "base": 2, "stops": )" + stops + R"(, "property": "x"})");
- return doc;
+ return R"({"type": "exponential", "base": 2, "stops": )" + stops + R"(, "property": "x"})";
}
static void Parse_CompositeFunction(benchmark::State& state) {
@@ -42,7 +35,7 @@ static void Parse_CompositeFunction(benchmark::State& state) {
state.PauseTiming();
auto doc = createFunctionJSON(stopCount);
state.ResumeTiming();
- optional<CompositeFunction<float>> result = conversion::convert<style::CompositeFunction<float>, JSValue>(doc, error);
+ optional<CompositeFunction<float>> result = conversion::convertJSON<style::CompositeFunction<float>>(doc, error);
if (!result) {
state.SkipWithError(error.message.c_str());
}
@@ -54,7 +47,7 @@ static void Evaluate_CompositeFunction(benchmark::State& state) {
size_t stopCount = state.range(0);
auto doc = createFunctionJSON(stopCount);
conversion::Error error;
- optional<CompositeFunction<float>> function = conversion::convert<CompositeFunction<float>, JSValue>(doc, error);
+ optional<CompositeFunction<float>> function = conversion::convertJSON<CompositeFunction<float>>(doc, error);
if (!function) {
state.SkipWithError(error.message.c_str());
}
diff --git a/benchmark/function/source_function.benchmark.cpp b/benchmark/function/source_function.benchmark.cpp
index 14e729eee2..af361943e3 100644
--- a/benchmark/function/source_function.benchmark.cpp
+++ b/benchmark/function/source_function.benchmark.cpp
@@ -4,19 +4,14 @@
#include <mbgl/style/function/source_function.hpp>
-#include <mbgl/style/rapidjson_conversion.hpp>
#include <mbgl/style/conversion.hpp>
+#include <mbgl/style/conversion/json.hpp>
#include <mbgl/style/conversion/function.hpp>
-#include <rapidjson/document.h>
-
-
using namespace mbgl;
using namespace mbgl::style;
-static rapidjson::GenericDocument<rapidjson::UTF8<>, rapidjson::CrtAllocator> createFunctionJSON(size_t stopCount) {
- rapidjson::GenericDocument<rapidjson::UTF8<>, rapidjson::CrtAllocator> doc;
-
+static std::string createFunctionJSON(size_t stopCount) {
std::string stops = "[";
for (size_t i = 0; i < stopCount; i++) {
std::string value = std::to_string(100.0f / stopCount * i);
@@ -24,9 +19,7 @@ static rapidjson::GenericDocument<rapidjson::UTF8<>, rapidjson::CrtAllocator> cr
stops += "[" + value + ", " + value + "]";
}
stops += "]";
-
- doc.Parse<0>(R"({"type": "exponential", "base": 2, "stops": )" + stops + R"(, "property": "x"})");
- return doc;
+ return R"({"type": "exponential", "base": 2, "stops": )" + stops + R"(, "property": "x"})";
}
static void Parse_SourceFunction(benchmark::State& state) {
@@ -37,7 +30,7 @@ static void Parse_SourceFunction(benchmark::State& state) {
state.PauseTiming();
auto doc = createFunctionJSON(stopCount);
state.ResumeTiming();
- optional<SourceFunction<float>> result = conversion::convert<SourceFunction<float>, JSValue>(doc, error);
+ optional<SourceFunction<float>> result = conversion::convertJSON<SourceFunction<float>>(doc, error);
if (!result) {
state.SkipWithError(error.message.c_str());
}
@@ -49,7 +42,7 @@ static void Evaluate_SourceFunction(benchmark::State& state) {
size_t stopCount = state.range(0);
auto doc = createFunctionJSON(stopCount);
conversion::Error error;
- optional<SourceFunction<float>> function = conversion::convert<SourceFunction<float>, JSValue>(doc, error);
+ optional<SourceFunction<float>> function = conversion::convertJSON<SourceFunction<float>>(doc, error);
if (!function) {
state.SkipWithError(error.message.c_str());
}
diff --git a/benchmark/parse/filter.benchmark.cpp b/benchmark/parse/filter.benchmark.cpp
index d650cb72c9..4984668400 100644
--- a/benchmark/parse/filter.benchmark.cpp
+++ b/benchmark/parse/filter.benchmark.cpp
@@ -2,20 +2,16 @@
#include <mbgl/style/filter.hpp>
#include <mbgl/style/filter_evaluator.hpp>
-#include <mbgl/style/rapidjson_conversion.hpp>
#include <mbgl/style/conversion.hpp>
+#include <mbgl/style/conversion/json.hpp>
#include <mbgl/style/conversion/filter.hpp>
#include <mbgl/tile/geometry_tile_data.hpp>
-#include <rapidjson/document.h>
-
using namespace mbgl;
style::Filter parse(const char* expression) {
- rapidjson::GenericDocument<rapidjson::UTF8<>, rapidjson::CrtAllocator> doc;
- doc.Parse<0>(expression);
style::conversion::Error error;
- return *style::conversion::convert<style::Filter, JSValue>(doc, error);
+ return *style::conversion::convertJSON<style::Filter>(expression, error);
}
static void Parse_Filter(benchmark::State& state) {
diff --git a/cmake/benchmark.cmake b/cmake/benchmark.cmake
index 87351e97b1..623483bedf 100644
--- a/cmake/benchmark.cmake
+++ b/cmake/benchmark.cmake
@@ -19,6 +19,7 @@ target_link_libraries(mbgl-benchmark
target_add_mason_package(mbgl-benchmark PRIVATE boost)
target_add_mason_package(mbgl-benchmark PRIVATE benchmark)
+target_add_mason_package(mbgl-benchmark PRIVATE geojson)
target_add_mason_package(mbgl-benchmark PRIVATE rapidjson)
target_add_mason_package(mbgl-benchmark PRIVATE protozero)
target_add_mason_package(mbgl-benchmark PRIVATE vector-tile)
diff --git a/cmake/core-files.cmake b/cmake/core-files.cmake
index 54b4079cff..f70bbb943f 100644
--- a/cmake/core-files.cmake
+++ b/cmake/core-files.cmake
@@ -156,6 +156,7 @@ set(MBGL_CORE_FILES
# renderer
include/mbgl/renderer/backend_scope.hpp
+ include/mbgl/renderer/mode.hpp
include/mbgl/renderer/query.hpp
include/mbgl/renderer/renderer.hpp
include/mbgl/renderer/renderer_backend.hpp
@@ -364,22 +365,81 @@ set(MBGL_CORE_FILES
include/mbgl/style/conversion/constant.hpp
include/mbgl/style/conversion/coordinate.hpp
include/mbgl/style/conversion/data_driven_property_value.hpp
+ include/mbgl/style/conversion/expression.hpp
include/mbgl/style/conversion/filter.hpp
include/mbgl/style/conversion/function.hpp
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/layer.hpp
include/mbgl/style/conversion/light.hpp
- include/mbgl/style/conversion/make_property_setters.hpp
include/mbgl/style/conversion/position.hpp
- include/mbgl/style/conversion/property_setter.hpp
include/mbgl/style/conversion/property_value.hpp
include/mbgl/style/conversion/source.hpp
include/mbgl/style/conversion/tileset.hpp
include/mbgl/style/conversion/transition_options.hpp
+ src/mbgl/style/conversion/constant.cpp
+ src/mbgl/style/conversion/coordinate.cpp
+ src/mbgl/style/conversion/filter.cpp
src/mbgl/style/conversion/geojson.cpp
+ src/mbgl/style/conversion/geojson_options.cpp
+ src/mbgl/style/conversion/get_json_type.cpp
src/mbgl/style/conversion/json.hpp
+ src/mbgl/style/conversion/layer.cpp
+ src/mbgl/style/conversion/light.cpp
+ src/mbgl/style/conversion/make_property_setters.hpp
+ src/mbgl/style/conversion/position.cpp
+ src/mbgl/style/conversion/property_setter.hpp
+ src/mbgl/style/conversion/source.cpp
src/mbgl/style/conversion/stringify.hpp
+ src/mbgl/style/conversion/tileset.cpp
+ src/mbgl/style/conversion/transition_options.cpp
+
+ # style/expression
+ include/mbgl/style/expression/array_assertion.hpp
+ include/mbgl/style/expression/assertion.hpp
+ include/mbgl/style/expression/at.hpp
+ include/mbgl/style/expression/boolean_operator.hpp
+ include/mbgl/style/expression/case.hpp
+ include/mbgl/style/expression/check_subtype.hpp
+ include/mbgl/style/expression/coalesce.hpp
+ include/mbgl/style/expression/coercion.hpp
+ include/mbgl/style/expression/compound_expression.hpp
+ include/mbgl/style/expression/expression.hpp
+ include/mbgl/style/expression/find_zoom_curve.hpp
+ include/mbgl/style/expression/get_covering_stops.hpp
+ include/mbgl/style/expression/interpolate.hpp
+ include/mbgl/style/expression/is_constant.hpp
+ include/mbgl/style/expression/is_expression.hpp
+ include/mbgl/style/expression/let.hpp
+ include/mbgl/style/expression/literal.hpp
+ include/mbgl/style/expression/match.hpp
+ include/mbgl/style/expression/parsing_context.hpp
+ include/mbgl/style/expression/step.hpp
+ include/mbgl/style/expression/type.hpp
+ include/mbgl/style/expression/value.hpp
+ src/mbgl/style/expression/array_assertion.cpp
+ src/mbgl/style/expression/assertion.cpp
+ src/mbgl/style/expression/at.cpp
+ src/mbgl/style/expression/boolean_operator.cpp
+ src/mbgl/style/expression/case.cpp
+ src/mbgl/style/expression/check_subtype.cpp
+ src/mbgl/style/expression/coalesce.cpp
+ src/mbgl/style/expression/coercion.cpp
+ src/mbgl/style/expression/compound_expression.cpp
+ src/mbgl/style/expression/find_zoom_curve.cpp
+ src/mbgl/style/expression/get_covering_stops.cpp
+ src/mbgl/style/expression/interpolate.cpp
+ src/mbgl/style/expression/is_constant.cpp
+ src/mbgl/style/expression/is_expression.cpp
+ src/mbgl/style/expression/let.cpp
+ src/mbgl/style/expression/literal.cpp
+ src/mbgl/style/expression/match.cpp
+ src/mbgl/style/expression/parsing_context.cpp
+ src/mbgl/style/expression/step.cpp
+ src/mbgl/style/expression/util.cpp
+ src/mbgl/style/expression/util.hpp
+ src/mbgl/style/expression/value.cpp
# style/function
include/mbgl/style/function/camera_function.hpp
@@ -388,11 +448,13 @@ set(MBGL_CORE_FILES
include/mbgl/style/function/composite_exponential_stops.hpp
include/mbgl/style/function/composite_function.hpp
include/mbgl/style/function/composite_interval_stops.hpp
+ include/mbgl/style/function/convert.hpp
include/mbgl/style/function/exponential_stops.hpp
include/mbgl/style/function/identity_stops.hpp
include/mbgl/style/function/interval_stops.hpp
include/mbgl/style/function/source_function.hpp
src/mbgl/style/function/categorical_stops.cpp
+ src/mbgl/style/function/expression.cpp
src/mbgl/style/function/identity_stops.cpp
# style/layers
diff --git a/cmake/node.cmake b/cmake/node.cmake
index c256b13f93..3f7bcdb784 100644
--- a/cmake/node.cmake
+++ b/cmake/node.cmake
@@ -13,6 +13,7 @@ set_target_properties("mbgl-node" PROPERTIES CXX_STANDARD 14)
target_sources(mbgl-node
PRIVATE platform/node/src/node_logging.hpp
PRIVATE platform/node/src/node_logging.cpp
+ PRIVATE platform/node/src/node_conversion.hpp
PRIVATE platform/node/src/node_map.hpp
PRIVATE platform/node/src/node_map.cpp
PRIVATE platform/node/src/node_request.hpp
@@ -21,6 +22,8 @@ target_sources(mbgl-node
PRIVATE platform/node/src/node_feature.cpp
PRIVATE platform/node/src/node_thread_pool.hpp
PRIVATE platform/node/src/node_thread_pool.cpp
+ PRIVATE platform/node/src/node_expression.hpp
+ PRIVATE platform/node/src/node_expression.cpp
PRIVATE platform/node/src/util/async_queue.hpp
)
@@ -93,6 +96,17 @@ xcode_create_scheme(
xcode_create_scheme(
TARGET mbgl-node
TYPE node
+ NAME "node expression tests"
+ ARGS
+ "platform/node/test/expression.test.js"
+ OPTIONAL_ARGS
+ "group"
+ "test"
+)
+
+xcode_create_scheme(
+ TARGET mbgl-node
+ TYPE node
NAME "node-benchmark"
ARGS
"platform/node/test/benchmark.js"
diff --git a/cmake/render.cmake b/cmake/render.cmake
index f69aed16c0..aff9397f42 100644
--- a/cmake/render.cmake
+++ b/cmake/render.cmake
@@ -16,6 +16,7 @@ target_link_libraries(mbgl-render
target_add_mason_package(mbgl-render PRIVATE boost)
target_add_mason_package(mbgl-render PRIVATE boost_libprogram_options)
+target_add_mason_package(mbgl-render PRIVATE geojson)
mbgl_platform_render()
diff --git a/cmake/test-files.cmake b/cmake/test-files.cmake
index 027c34f31e..319790f05a 100644
--- a/cmake/test-files.cmake
+++ b/cmake/test-files.cmake
@@ -23,6 +23,7 @@ set(MBGL_TEST_FILES
# gl
test/gl/bucket.test.cpp
test/gl/object.test.cpp
+ test/gl/context.test.cpp
# include/mbgl
test/include/mbgl/test.hpp
@@ -51,7 +52,6 @@ set(MBGL_TEST_FILES
test/sprite/sprite_parser.test.cpp
# src/mbgl/test
- test/src/mbgl/test/conversion_stubs.hpp
test/src/mbgl/test/fake_file_source.hpp
test/src/mbgl/test/fixture_log_observer.cpp
test/src/mbgl/test/fixture_log_observer.hpp
@@ -88,6 +88,10 @@ set(MBGL_TEST_FILES
test/style/conversion/light.test.cpp
test/style/conversion/stringify.test.cpp
+ # style/expression
+ test/style/expression/expression.test.cpp
+ test/style/expression/util.test.cpp
+
# style
test/style/filter.test.cpp
diff --git a/include/mbgl/map/map.hpp b/include/mbgl/map/map.hpp
index c5f90d99e1..5ba23a76dd 100644
--- a/include/mbgl/map/map.hpp
+++ b/include/mbgl/map/map.hpp
@@ -127,6 +127,14 @@ public:
void setViewportMode(ViewportMode);
ViewportMode getViewportMode() const;
+ // Projection mode
+ void setAxonometric(bool);
+ bool getAxonometric() const;
+ void setXSkew(double ySkew);
+ double getXSkew() const;
+ void setYSkew(double ySkew);
+ double getYSkew() const;
+
// Size
void setSize(Size);
Size getSize() const;
diff --git a/include/mbgl/map/mode.hpp b/include/mbgl/map/mode.hpp
index 05de2df22c..256d152e43 100644
--- a/include/mbgl/map/mode.hpp
+++ b/include/mbgl/map/mode.hpp
@@ -14,15 +14,6 @@ enum class MapMode : EnumType {
Still, // a once-off still image
};
-// We can avoid redundant GL calls when it is known that the GL context is not
-// being shared. In a shared GL context case, we need to make sure that the
-// correct GL configurations are in use - they might have changed between render
-// calls.
-enum class GLContextMode : EnumType {
- Unique,
- Shared,
-};
-
// We can choose to constrain the map both horizontally or vertically, or only
// vertically e.g. while panning.
enum class ConstrainMode : EnumType {
diff --git a/include/mbgl/renderer/mode.hpp b/include/mbgl/renderer/mode.hpp
new file mode 100644
index 0000000000..6ff42d8058
--- /dev/null
+++ b/include/mbgl/renderer/mode.hpp
@@ -0,0 +1,18 @@
+#pragma once
+
+#include <cstdint>
+
+namespace mbgl {
+
+using EnumType = uint32_t;
+
+// We can avoid redundant GL calls when it is known that the GL context is not
+// being shared. In a shared GL context case, we need to make sure that the
+// correct GL configurations are in use - they might have changed between render
+// calls.
+enum class GLContextMode : EnumType {
+ Unique,
+ Shared,
+};
+
+} // namespace mbgl
diff --git a/include/mbgl/renderer/renderer.hpp b/include/mbgl/renderer/renderer.hpp
index 23d2451a2e..0937e9334b 100644
--- a/include/mbgl/renderer/renderer.hpp
+++ b/include/mbgl/renderer/renderer.hpp
@@ -1,7 +1,7 @@
#pragma once
-#include <mbgl/map/mode.hpp>
#include <mbgl/renderer/query.hpp>
+#include <mbgl/renderer/mode.hpp>
#include <mbgl/annotation/annotation.hpp>
#include <mbgl/util/geo.hpp>
#include <mbgl/util/geo.hpp>
diff --git a/include/mbgl/storage/offline.hpp b/include/mbgl/storage/offline.hpp
index 117dd0591b..ef4a499e83 100644
--- a/include/mbgl/storage/offline.hpp
+++ b/include/mbgl/storage/offline.hpp
@@ -30,15 +30,15 @@ public:
OfflineTilePyramidRegionDefinition(std::string, LatLngBounds, double, double, float);
/* Private */
- std::vector<CanonicalTileID> tileCover(SourceType, uint16_t tileSize, const Range<uint8_t>& zoomRange) const;
- uint64_t tileCount(SourceType, uint16_t tileSize, const Range<uint8_t>& zoomRange) const;
+ std::vector<CanonicalTileID> tileCover(style::SourceType, uint16_t tileSize, const Range<uint8_t>& zoomRange) const;
+ uint64_t tileCount(style::SourceType, uint16_t tileSize, const Range<uint8_t>& zoomRange) const;
const std::string styleURL;
const LatLngBounds bounds;
const double minZoom;
const double maxZoom;
const float pixelRatio;
private:
- Range<uint8_t> coveringZoomRange(SourceType, uint16_t tileSize, const Range<uint8_t>& zoomRange) const;
+ Range<uint8_t> coveringZoomRange(style::SourceType, uint16_t tileSize, const Range<uint8_t>& zoomRange) const;
};
/*
diff --git a/include/mbgl/style/conversion.hpp b/include/mbgl/style/conversion.hpp
index 27504a89b1..0b7e0b2b2f 100644
--- a/include/mbgl/style/conversion.hpp
+++ b/include/mbgl/style/conversion.hpp
@@ -1,6 +1,8 @@
#pragma once
#include <mbgl/util/optional.hpp>
+#include <mbgl/util/feature.hpp>
+#include <mbgl/util/geojson.hpp>
#include <string>
@@ -9,9 +11,8 @@ namespace style {
namespace conversion {
/*
- The `conversion` namespace defines conversions from a templated type `V` representing a JSON
- object conforming to the schema defined by the Mapbox Style Specification, to the various C++
- types that form the C++ model of that domain:
+ The `conversion` namespace defines conversions from JSON structures conforming to the schema defined by
+ the Mapbox Style Specification, to the various C++ types that form the C++ model of that domain:
* `std::unique_ptr<Source>`
* `std::unique_ptr<Layer>`
@@ -20,15 +21,31 @@ namespace conversion {
A single template function serves as the public interface:
- template <class T, class V>
- optional<T> convert(const V& value, Error& error);
+ template <class T>
+ optional<T> convert(const Convertible& input, Error& error);
Where `T` is one of the above types. If the conversion fails, the result is empty, and the
error parameter includes diagnostic text suitable for presentation to a library user. Otherwise,
a filled optional is returned.
- The implementation of `convert` requires that the following are legal expressions for a value `v`
- of type `const V&`:
+ `Convertible` is a type that encapsulates a special form of polymorphism over various underlying types that
+ can serve as input to the conversion algorithm. For instance, on macOS, we need to support
+ conversion from both RapidJSON types, and a JSON structure represented with `NSArray`/`NSDictionary`/etc.
+ On Qt, we need to support conversion from RapidJSON types and QVariant.
+
+ We don't want to use traditional forms of polymorphism to accomplish this:
+
+ * Compile time polymorphism using a template parameter for the actual value type leads to
+ excessive code bloat and long compile times.
+ * Runtime polymorphism using virtual methods requires extra heap allocation and ubiquitous
+ use of std::unique_ptr, unsuitable for this performance-sensitive code.
+
+ Therefore, we're using a custom implementation of runtime polymorphism where we manually create and
+ dispatch through a table of function pointers (vtable), while keeping the storage for any of the possible
+ underlying types inline on the stack, using `std::aligned_storage`.
+
+ For a given underlying type T, an explicit specialization of `ConversionTraits<T>` must be provided. This
+ specialization must provide the following static methods:
* `isUndefined(v)` -- returns a boolean indication whether `v` is undefined or a JSON null
@@ -48,21 +65,234 @@ namespace conversion {
* `toNumber(v)` -- returns `optional<float>`, absence indicating `v` is not a JSON number
* `toDouble(v)` -- returns `optional<double>`, absence indicating `v` is not a JSON number
* `toString(v)` -- returns `optional<std::string>`, absence indicating `v` is not a JSON string
- * `toValue(v)` -- returns `optional<mbgl::Value>`, a variant type, for generic conversion,
+ * `toValue(v)` -- returns `optional<Value>`, a variant type, for generic conversion,
absence indicating `v` is not a boolean, number, or string. Numbers should be converted to
unsigned integer, signed integer, or floating point, in descending preference.
- The mbgl core implements these requirements for RapidJSON types, and the node bindings implement
- them for v8 types.
+ In addition, the type T must be move-constructable. And finally, `Convertible::Storage`, a typedef for
+ `std::aligned_storage_t`, must be large enough to satisfy the memory requirements for any of the
+ possible underlying types. (A static assert will fail if this is not the case.)
+
+ `Convertible` itself is movable, but not copyable. A moved-from `Convertible` is in an invalid state;
+ you must not do anything with it except let it go out of scope.
*/
struct Error { std::string message; };
+template <typename T>
+class ConversionTraits;
+
+class Convertible {
+public:
+ template <typename T>
+ Convertible(T&& value) : vtable(vtableForType<std::decay_t<T>>()) {
+ static_assert(sizeof(Storage) >= sizeof(std::decay_t<T>), "Storage must be large enough to hold value type");
+ new (static_cast<void*>(&storage)) std::decay_t<T>(std::forward<T>(value));
+ }
+
+ Convertible(Convertible&& v)
+ : vtable(v.vtable)
+ {
+ if (vtable) {
+ vtable->move(std::move(v.storage), this->storage);
+ }
+ }
+
+ ~Convertible() {
+ if (vtable) {
+ vtable->destroy(storage);
+ }
+ }
+
+ Convertible& operator=(Convertible&& v) {
+ if (vtable) {
+ vtable->destroy(storage);
+ }
+ vtable = v.vtable;
+ if (vtable) {
+ vtable->move(std::move(v.storage), this->storage);
+ }
+ v.vtable = nullptr;
+ return *this;
+ }
+
+ Convertible() = delete;
+ Convertible(const Convertible&) = delete;
+ Convertible& operator=(const Convertible&) = delete;
+
+ friend inline bool isUndefined(const Convertible& v) {
+ assert(v.vtable);
+ return v.vtable->isUndefined(v.storage);
+ }
+
+ friend inline bool isArray(const Convertible& v) {
+ assert(v.vtable);
+ return v.vtable->isArray(v.storage);
+ }
+
+ friend inline std::size_t arrayLength(const Convertible& v) {
+ assert(v.vtable);
+ return v.vtable->arrayLength(v.storage);
+ }
+
+ friend inline Convertible arrayMember(const Convertible& v, std::size_t i) {
+ assert(v.vtable);
+ return v.vtable->arrayMember(v.storage, i);
+ }
+
+ friend inline bool isObject(const Convertible& v) {
+ assert(v.vtable);
+ return v.vtable->isObject(v.storage);
+ }
+
+ friend inline optional<Convertible> objectMember(const Convertible& v, const char * name) {
+ assert(v.vtable);
+ return v.vtable->objectMember(v.storage, name);
+ }
+
+ friend inline optional<Error> eachMember(const Convertible& v, const std::function<optional<Error> (const std::string&, const Convertible&)>& fn) {
+ assert(v.vtable);
+ return v.vtable->eachMember(v.storage, fn);
+ }
+
+ friend inline optional<bool> toBool(const Convertible& v) {
+ assert(v.vtable);
+ return v.vtable->toBool(v.storage);
+ }
+
+ friend inline optional<float> toNumber(const Convertible& v) {
+ assert(v.vtable);
+ return v.vtable->toNumber(v.storage);
+ }
+
+ friend inline optional<double> toDouble(const Convertible& v) {
+ assert(v.vtable);
+ return v.vtable->toDouble(v.storage);
+ }
+
+ friend inline optional<std::string> toString(const Convertible& v) {
+ assert(v.vtable);
+ return v.vtable->toString(v.storage);
+ }
+
+ friend inline optional<Value> toValue(const Convertible& v) {
+ assert(v.vtable);
+ return v.vtable->toValue(v.storage);
+ }
+
+ friend inline optional<GeoJSON> toGeoJSON(const Convertible& v, Error& error) {
+ assert(v.vtable);
+ return v.vtable->toGeoJSON(v.storage, error);
+ }
+
+private:
+#if __ANDROID__
+ // Android: JSValue* or mbgl::android::Value
+ using Storage = std::aligned_storage_t<32, 8>;
+#elif __QT__
+ // Qt: JSValue* or QVariant
+ using Storage = std::aligned_storage_t<32, 8>;
+#else
+ // Node: JSValue* or v8::Local<v8::Value>
+ // iOS/macOS: JSValue* or id
+ using Storage = std::aligned_storage_t<8, 8>;
+#endif
+
+ struct VTable {
+ void (*move) (Storage&& src, Storage& dest);
+ void (*destroy) (Storage&);
+
+ bool (*isUndefined) (const Storage&);
+
+ bool (*isArray) (const Storage&);
+ std::size_t (*arrayLength) (const Storage&);
+ Convertible (*arrayMember) (const Storage&, std::size_t);
+
+ bool (*isObject) (const Storage&);
+ optional<Convertible> (*objectMember) (const Storage&, const char *);
+ optional<Error> (*eachMember) (const Storage&, const std::function<optional<Error> (const std::string&, const Convertible&)>&);
+
+ optional<bool> (*toBool) (const Storage&);
+ optional<float> (*toNumber) (const Storage&);
+ optional<double> (*toDouble) (const Storage&);
+ optional<std::string> (*toString) (const Storage&);
+ optional<Value> (*toValue) (const Storage&);
+
+ // https://github.com/mapbox/mapbox-gl-native/issues/5623
+ optional<GeoJSON> (*toGeoJSON) (const Storage&, Error&);
+ };
+
+ template <typename T>
+ static VTable* vtableForType() {
+ using Traits = ConversionTraits<T>;
+ static VTable vtable = {
+ [] (Storage&& src, Storage& dest) {
+ auto srcValue = reinterpret_cast<T&&>(src);
+ new (static_cast<void*>(&dest)) T(std::move(srcValue));
+ srcValue.~T();
+ },
+ [] (Storage& s) {
+ reinterpret_cast<T&>(s).~T();
+ },
+ [] (const Storage& s) {
+ return Traits::isUndefined(reinterpret_cast<const T&>(s));
+ },
+ [] (const Storage& s) {
+ return Traits::isArray(reinterpret_cast<const T&>(s));
+ },
+ [] (const Storage& s) {
+ return Traits::arrayLength(reinterpret_cast<const T&>(s));
+ },
+ [] (const Storage& s, std::size_t i) {
+ return Convertible(Traits::arrayMember(reinterpret_cast<const T&>(s), i));
+ },
+ [] (const Storage& s) {
+ return Traits::isObject(reinterpret_cast<const T&>(s));
+ },
+ [] (const Storage& s, const char * key) {
+ optional<T> member = Traits::objectMember(reinterpret_cast<const T&>(s), key);
+ if (member) {
+ return optional<Convertible>(Convertible(std::move(*member)));
+ } else {
+ return optional<Convertible>();
+ }
+ },
+ [] (const Storage& s, const std::function<optional<Error> (const std::string&, const Convertible&)>& fn) {
+ return Traits::eachMember(reinterpret_cast<const T&>(s), [&](const std::string& k, T&& v) {
+ return fn(k, Convertible(std::move(v)));
+ });
+ },
+ [] (const Storage& s) {
+ return Traits::toBool(reinterpret_cast<const T&>(s));
+ },
+ [] (const Storage& s) {
+ return Traits::toNumber(reinterpret_cast<const T&>(s));
+ },
+ [] (const Storage& s) {
+ return Traits::toDouble(reinterpret_cast<const T&>(s));
+ },
+ [] (const Storage& s) {
+ return Traits::toString(reinterpret_cast<const T&>(s));
+ },
+ [] (const Storage& s) {
+ return Traits::toValue(reinterpret_cast<const T&>(s));
+ },
+ [] (const Storage& s, Error& err) {
+ return Traits::toGeoJSON(reinterpret_cast<const T&>(s), err);
+ }
+ };
+ return &vtable;
+ }
+
+ VTable* vtable;
+ Storage storage;
+};
+
template <class T, class Enable = void>
struct Converter;
-template <class T, class V, class...Args>
-optional<T> convert(const V& value, Error& error, Args&&...args) {
+template <class T, class...Args>
+optional<T> convert(const Convertible& value, Error& error, Args&&...args) {
return Converter<T>()(value, error, std::forward<Args>(args)...);
}
diff --git a/include/mbgl/style/conversion/constant.hpp b/include/mbgl/style/conversion/constant.hpp
index 07c0a35fae..7b3249da52 100644
--- a/include/mbgl/style/conversion/constant.hpp
+++ b/include/mbgl/style/conversion/constant.hpp
@@ -1,7 +1,6 @@
#pragma once
#include <mbgl/style/conversion.hpp>
-#include <mbgl/util/optional.hpp>
#include <mbgl/util/color.hpp>
#include <mbgl/util/enum.hpp>
#include <mbgl/util/string.hpp>
@@ -16,47 +15,22 @@ namespace conversion {
template <>
struct Converter<bool> {
- template <class V>
- optional<bool> operator()(const V& value, Error& error) const {
- optional<bool> converted = toBool(value);
- if (!converted) {
- error = { "value must be a boolean" };
- return {};
- }
- return *converted;
- }
+ optional<bool> operator()(const Convertible& value, Error& error) const;
};
template <>
struct Converter<float> {
- template <class V>
- optional<float> operator()(const V& value, Error& error) const {
- optional<float> converted = toNumber(value);
- if (!converted) {
- error = { "value must be a number" };
- return {};
- }
- return *converted;
- }
+ optional<float> operator()(const Convertible& value, Error& error) const;
};
template <>
struct Converter<std::string> {
- template <class V>
- optional<std::string> operator()(const V& value, Error& error) const {
- optional<std::string> converted = toString(value);
- if (!converted) {
- error = { "value must be a string" };
- return {};
- }
- return *converted;
- }
+ optional<std::string> operator()(const Convertible& value, Error& error) const;
};
template <class T>
struct Converter<T, typename std::enable_if_t<std::is_enum<T>::value>> {
- template <class V>
- optional<T> operator()(const V& value, Error& error) const {
+ optional<T> operator()(const Convertible& value, Error& error) const {
optional<std::string> string = toString(value);
if (!string) {
error = { "value must be a string" };
@@ -75,28 +49,12 @@ struct Converter<T, typename std::enable_if_t<std::is_enum<T>::value>> {
template <>
struct Converter<Color> {
- template <class V>
- optional<Color> operator()(const V& value, Error& error) const {
- optional<std::string> string = toString(value);
- if (!string) {
- error = { "value must be a string" };
- return {};
- }
-
- optional<Color> color = Color::parse(*string);
- if (!color) {
- error = { "value must be a valid color" };
- return {};
- }
-
- return *color;
- }
+ optional<Color> operator()(const Convertible& value, Error& error) const;
};
template <size_t N>
struct Converter<std::array<float, N>> {
- template <class V>
- optional<std::array<float, N>> operator()(const V& value, Error& error) const {
+ optional<std::array<float, N>> operator()(const Convertible& value, Error& error) const {
if (!isArray(value) || arrayLength(value) != N) {
error = { "value must be an array of " + util::toString(N) + " numbers" };
return {};
@@ -117,52 +75,12 @@ struct Converter<std::array<float, N>> {
template <>
struct Converter<std::vector<float>> {
- template <class V>
- optional<std::vector<float>> operator()(const V& value, Error& error) const {
- if (!isArray(value)) {
- error = { "value must be an array" };
- return {};
- }
-
- std::vector<float> result;
- result.reserve(arrayLength(value));
-
- for (std::size_t i = 0; i < arrayLength(value); ++i) {
- optional<float> number = toNumber(arrayMember(value, i));
- if (!number) {
- error = { "value must be an array of numbers" };
- return {};
- }
- result.push_back(*number);
- }
-
- return result;
- }
+ optional<std::vector<float>> operator()(const Convertible& value, Error& error) const;
};
template <>
struct Converter<std::vector<std::string>> {
- template <class V>
- optional<std::vector<std::string>> operator()(const V& value, Error& error) const {
- if (!isArray(value)) {
- error = { "value must be an array" };
- return {};
- }
-
- std::vector<std::string> result;
- result.reserve(arrayLength(value));
-
- for (std::size_t i = 0; i < arrayLength(value); ++i) {
- optional<std::string> string = toString(arrayMember(value, i));
- if (!string) {
- error = { "value must be an array of strings" };
- return {};
- }
- result.push_back(*string);
- }
-
- return result;
- }
+ optional<std::vector<std::string>> operator()(const Convertible& value, Error& error) const;
};
} // namespace conversion
diff --git a/include/mbgl/style/conversion/coordinate.hpp b/include/mbgl/style/conversion/coordinate.hpp
index 732624e77f..e11db5e32f 100644
--- a/include/mbgl/style/conversion/coordinate.hpp
+++ b/include/mbgl/style/conversion/coordinate.hpp
@@ -10,26 +10,7 @@ namespace conversion {
template<>
struct Converter<LatLng> {
public:
- template <class V>
- optional<LatLng> operator() (const V& value, Error& error) const {
- if (!isArray(value) || arrayLength(value) < 2 ) {
- error = { "coordinate array must contain numeric longitude and latitude values" };
- return {};
- }
- //Style spec uses GeoJSON convention for specifying coordinates
- optional<double> latitude = toDouble(arrayMember(value, 1));
- optional<double> longitude = toDouble(arrayMember(value, 0));
-
- if (!latitude || !longitude) {
- error = { "coordinate array must contain numeric longitude and latitude values" };
- return {};
- }
- if (*latitude < -90 || *latitude > 90 ){
- error = { "coordinate latitude must be between -90 and 90" };
- return {};
- }
- return LatLng(*latitude, *longitude);
- }
+ optional<LatLng> operator() (const Convertible& value, Error& error) const;
};
} // namespace conversion
diff --git a/include/mbgl/style/conversion/data_driven_property_value.hpp b/include/mbgl/style/conversion/data_driven_property_value.hpp
index 79b15dcfb0..8880d28fb1 100644
--- a/include/mbgl/style/conversion/data_driven_property_value.hpp
+++ b/include/mbgl/style/conversion/data_driven_property_value.hpp
@@ -4,6 +4,13 @@
#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/is_expression.hpp>
+#include <mbgl/style/expression/is_constant.hpp>
+#include <mbgl/style/expression/find_zoom_curve.hpp>
+
+#include <unordered_set>
+
namespace mbgl {
namespace style {
@@ -11,10 +18,27 @@ namespace conversion {
template <class T>
struct Converter<DataDrivenPropertyValue<T>> {
- template <class V>
- optional<DataDrivenPropertyValue<T>> operator()(const V& value, Error& error) const {
+
+ optional<DataDrivenPropertyValue<T>> operator()(const Convertible& value, Error& error) const {
if (isUndefined(value)) {
return DataDrivenPropertyValue<T>();
+ } else if (expression::isExpression(value)) {
+ optional<std::unique_ptr<Expression>> expression = convert<std::unique_ptr<Expression>>(
+ value,
+ error,
+ valueTypeToExpressionType<T>());
+
+ if (!expression) {
+ return {};
+ }
+
+ if (isFeatureConstant(**expression)) {
+ return DataDrivenPropertyValue<T>(CameraFunction<T>(std::move(*expression)));
+ } else if (isZoomConstant(**expression)) {
+ return DataDrivenPropertyValue<T>(SourceFunction<T>(std::move(*expression)));
+ } else {
+ return DataDrivenPropertyValue<T>(CompositeFunction<T>(std::move(*expression)));
+ }
} else if (!isObject(value)) {
optional<T> constant = convert<T>(value, error);
if (!constant) {
diff --git a/include/mbgl/style/conversion/expression.hpp b/include/mbgl/style/conversion/expression.hpp
new file mode 100644
index 0000000000..c5fcf906a7
--- /dev/null
+++ b/include/mbgl/style/conversion/expression.hpp
@@ -0,0 +1,39 @@
+#pragma once
+
+#include <mbgl/style/expression/parsing_context.hpp>
+#include <mbgl/style/expression/type.hpp>
+#include <mbgl/style/conversion.hpp>
+
+#include <memory>
+
+namespace mbgl {
+namespace style {
+namespace conversion {
+
+using namespace mbgl::style::expression;
+
+template<> struct Converter<std::unique_ptr<Expression>> {
+ optional<std::unique_ptr<Expression>> operator()(const Convertible& value, Error& error, type::Type expected) const {
+ ParsingContext ctx(optional<type::Type> {expected});
+ ParseResult parsed = ctx.parse(value);
+ if (parsed) {
+ return std::move(*parsed);
+ }
+ std::string combinedError;
+ for (const ParsingError& parsingError : ctx.getErrors()) {
+ if (combinedError.size() > 0) {
+ combinedError += "\n";
+ }
+ if (parsingError.key.size() > 0) {
+ combinedError += parsingError.key + ": ";
+ }
+ combinedError += parsingError.message;
+ }
+ error = { combinedError };
+ return {};
+ };
+};
+
+} // namespace conversion
+} // namespace style
+} // namespace mbgl
diff --git a/include/mbgl/style/conversion/filter.hpp b/include/mbgl/style/conversion/filter.hpp
index 986d1bf80d..9daf6ea7a4 100644
--- a/include/mbgl/style/conversion/filter.hpp
+++ b/include/mbgl/style/conversion/filter.hpp
@@ -2,7 +2,6 @@
#include <mbgl/style/filter.hpp>
#include <mbgl/style/conversion.hpp>
-#include <mbgl/util/geometry.hpp>
namespace mbgl {
namespace style {
@@ -11,247 +10,7 @@ namespace conversion {
template <>
struct Converter<Filter> {
public:
- template <class V>
- optional<Filter> operator()(const V& value, Error& error) const {
- if (!isArray(value)) {
- error = { "filter expression must be an array" };
- return {};
- }
-
- if (arrayLength(value) < 1) {
- error = { "filter expression must have at least 1 element" };
- return {};
- }
-
- optional<std::string> op = toString(arrayMember(value, 0));
- if (!op) {
- error = { "filter operator must be a string" };
- return {};
- }
-
- if (*op == "==") {
- return convertEqualityFilter<EqualsFilter, TypeEqualsFilter, IdentifierEqualsFilter>(value, error);
- } else if (*op == "!=") {
- return convertEqualityFilter<NotEqualsFilter, TypeNotEqualsFilter, IdentifierNotEqualsFilter>(value, error);
- } else if (*op == ">") {
- return convertBinaryFilter<GreaterThanFilter>(value, error);
- } else if (*op == ">=") {
- return convertBinaryFilter<GreaterThanEqualsFilter>(value, error);
- } else if (*op == "<") {
- return convertBinaryFilter<LessThanFilter>(value, error);
- } else if (*op == "<=") {
- return convertBinaryFilter<LessThanEqualsFilter>(value, error);
- } else if (*op == "in") {
- return convertSetFilter<InFilter, TypeInFilter, IdentifierInFilter>(value, error);
- } else if (*op == "!in") {
- return convertSetFilter<NotInFilter, TypeNotInFilter, IdentifierNotInFilter>(value, error);
- } else if (*op == "all") {
- return convertCompoundFilter<AllFilter>(value, error);
- } else if (*op == "any") {
- return convertCompoundFilter<AnyFilter>(value, error);
- } else if (*op == "none") {
- return convertCompoundFilter<NoneFilter>(value, error);
- } else if (*op == "has") {
- return convertUnaryFilter<HasFilter, HasIdentifierFilter>(value, error);
- } else if (*op == "!has") {
- return convertUnaryFilter<NotHasFilter, NotHasIdentifierFilter>(value, error);
- }
-
- error = { R"(filter operator must be one of "==", "!=", ">", ">=", "<", "<=", "in", "!in", "all", "any", "none", "has", or "!has")" };
- return {};
- }
-
-private:
- optional<Value> normalizeValue(const optional<Value>& value, Error& error) const {
- if (!value) {
- error = { "filter expression value must be a boolean, number, or string" };
- return {};
- } else {
- return *value;
- }
- }
-
- template <class V>
- optional<FeatureType> toFeatureType(const V& value, Error& error) const {
- optional<std::string> type = toString(value);
- if (!type) {
- error = { "value for $type filter must be a string" };
- return {};
- } else if (*type == "Point") {
- return FeatureType::Point;
- } else if (*type == "LineString") {
- return FeatureType::LineString;
- } else if (*type == "Polygon") {
- return FeatureType::Polygon;
- } else {
- error = { "value for $type filter must be Point, LineString, or Polygon" };
- return {};
- }
- }
-
- template <class V>
- optional<FeatureIdentifier> toFeatureIdentifier(const V& value, Error& error) const {
- optional<Value> identifier = toValue(value);
- if (!identifier) {
- error = { "filter expression value must be a boolean, number, or string" };
- return {};
- } else {
- return (*identifier).match(
- [] (uint64_t t) -> optional<FeatureIdentifier> { return { t }; },
- [] ( int64_t t) -> optional<FeatureIdentifier> { return { t }; },
- [] ( double t) -> optional<FeatureIdentifier> { return { t }; },
- [] (const std::string& t) -> optional<FeatureIdentifier> { return { t }; },
- [&] (const auto&) -> optional<FeatureIdentifier> {
- error = { "filter expression value must be a boolean, number, or string" };
- return {};
- });
- }
- }
-
- template <class FilterType, class IdentifierFilterType, class V>
- optional<Filter> convertUnaryFilter(const V& value, Error& error) const {
- if (arrayLength(value) < 2) {
- error = { "filter expression must have 2 elements" };
- return {};
- }
-
- optional<std::string> key = toString(arrayMember(value, 1));
- if (!key) {
- error = { "filter expression key must be a string" };
- return {};
- }
-
- if (*key == "$id") {
- return { IdentifierFilterType {} };
- } else {
- return { FilterType { *key } };
- }
- }
-
- template <class FilterType, class TypeFilterType, class IdentifierFilterType, class V>
- optional<Filter> convertEqualityFilter(const V& value, Error& error) const {
- if (arrayLength(value) < 3) {
- error = { "filter expression must have 3 elements" };
- return {};
- }
-
- optional<std::string> key = toString(arrayMember(value, 1));
- if (!key) {
- error = { "filter expression key must be a string" };
- return {};
- }
-
- if (*key == "$type") {
- optional<FeatureType> filterValue = toFeatureType(arrayMember(value, 2), error);
- if (!filterValue) {
- return {};
- }
-
- return { TypeFilterType { *filterValue } };
-
- } else if (*key == "$id") {
- optional<FeatureIdentifier> filterValue = toFeatureIdentifier(arrayMember(value, 2), error);
- if (!filterValue) {
- return {};
- }
-
- return { IdentifierFilterType { *filterValue } };
-
- } else {
- optional<Value> filterValue = normalizeValue(toValue(arrayMember(value, 2)), error);
- if (!filterValue) {
- return {};
- }
-
- return { FilterType { *key, *filterValue } };
- }
- }
-
- template <class FilterType, class V>
- optional<Filter> convertBinaryFilter(const V& value, Error& error) const {
- if (arrayLength(value) < 3) {
- error = { "filter expression must have 3 elements" };
- return {};
- }
-
- optional<std::string> key = toString(arrayMember(value, 1));
- if (!key) {
- error = { "filter expression key must be a string" };
- return {};
- }
-
- optional<Value> filterValue = normalizeValue(toValue(arrayMember(value, 2)), error);
- if (!filterValue) {
- return {};
- }
-
- return { FilterType { *key, *filterValue } };
- }
-
- template <class FilterType, class TypeFilterType, class IdentifierFilterType, class V>
- optional<Filter> convertSetFilter(const V& value, Error& error) const {
- if (arrayLength(value) < 2) {
- error = { "filter expression must at least 2 elements" };
- return {};
- }
-
- optional<std::string> key = toString(arrayMember(value, 1));
- if (!key) {
- error = { "filter expression key must be a string" };
- return {};
- }
-
- if (*key == "$type") {
- std::vector<FeatureType> values;
- for (std::size_t i = 2; i < arrayLength(value); ++i) {
- optional<FeatureType> filterValue = toFeatureType(arrayMember(value, i), error);
- if (!filterValue) {
- return {};
- }
- values.push_back(*filterValue);
- }
-
- return { TypeFilterType { std::move(values) } };
-
- } else if (*key == "$id") {
- std::vector<FeatureIdentifier> values;
- for (std::size_t i = 2; i < arrayLength(value); ++i) {
- optional<FeatureIdentifier> filterValue = toFeatureIdentifier(arrayMember(value, i), error);
- if (!filterValue) {
- return {};
- }
- values.push_back(*filterValue);
- }
-
- return { IdentifierFilterType { std::move(values) } };
-
- } else {
- std::vector<Value> values;
- for (std::size_t i = 2; i < arrayLength(value); ++i) {
- optional<Value> filterValue = normalizeValue(toValue(arrayMember(value, i)), error);
- if (!filterValue) {
- return {};
- }
- values.push_back(*filterValue);
- }
-
- return { FilterType { *key, std::move(values) } };
- }
- }
-
- template <class FilterType, class V>
- optional<Filter> convertCompoundFilter(const V& value, Error& error) const {
- std::vector<Filter> filters;
- for (std::size_t i = 1; i < arrayLength(value); ++i) {
- optional<Filter> element = operator()(arrayMember(value, i), error);
- if (!element) {
- return {};
- }
- filters.push_back(*element);
- }
-
- return { FilterType { std::move(filters) } };
- }
+ optional<Filter> operator()(const Convertible& value, Error& error) const;
};
} // namespace conversion
diff --git a/include/mbgl/style/conversion/function.hpp b/include/mbgl/style/conversion/function.hpp
index 752b6dd045..e230884944 100644
--- a/include/mbgl/style/conversion/function.hpp
+++ b/include/mbgl/style/conversion/function.hpp
@@ -11,8 +11,8 @@ namespace mbgl {
namespace style {
namespace conversion {
-template <class D, class R, class V>
-optional<std::map<D, R>> convertStops(const V& value, Error& error) {
+template <class D, class R>
+optional<std::map<D, R>> convertStops(const Convertible& value, Error& error) {
auto stopsValue = objectMember(value, "stops");
if (!stopsValue) {
error = { "function value must specify stops" };
@@ -63,8 +63,7 @@ template <class T>
struct Converter<ExponentialStops<T>> {
static constexpr const char * type = "exponential";
- template <class V>
- optional<ExponentialStops<T>> operator()(const V& value, Error& error) const {
+ optional<ExponentialStops<T>> operator()(const Convertible& value, Error& error) const {
auto stops = convertStops<float, T>(value, error);
if (!stops) {
return {};
@@ -89,8 +88,7 @@ template <class T>
struct Converter<IntervalStops<T>> {
static constexpr const char * type = "interval";
- template <class V>
- optional<IntervalStops<T>> operator()(const V& value, Error& error) const {
+ optional<IntervalStops<T>> operator()(const Convertible& value, Error& error) const {
auto stops = convertStops<float, T>(value, error);
if (!stops) {
return {};
@@ -101,8 +99,7 @@ struct Converter<IntervalStops<T>> {
template <>
struct Converter<CategoricalValue> {
- template <class V>
- optional<CategoricalValue> operator()(const V& value, Error& error) const {
+ optional<CategoricalValue> operator()(const Convertible& value, Error& error) const {
auto b = toBool(value);
if (b) {
return { *b };
@@ -127,8 +124,7 @@ template <class T>
struct Converter<CategoricalStops<T>> {
static constexpr const char * type = "categorical";
- template <class V>
- optional<CategoricalStops<T>> operator()(const V& value, Error& error) const {
+ optional<CategoricalStops<T>> operator()(const Convertible& value, Error& error) const {
auto stops = convertStops<CategoricalValue, T>(value, error);
if (!stops) {
return {};
@@ -142,8 +138,7 @@ template <class T>
struct Converter<IdentityStops<T>> {
static constexpr const char * type = "identity";
- template <class V>
- optional<IdentityStops<T>> operator()(const V&, Error&) const {
+ optional<IdentityStops<T>> operator()(const Convertible&, Error&) const {
return IdentityStops<T>();
}
};
@@ -154,8 +149,7 @@ struct StopsConverter;
template <class T, class... Ts>
struct StopsConverter<T, variant<Ts...>> {
public:
- template <class V>
- optional<variant<Ts...>> operator()(const V& value, Error& error) const {
+ optional<variant<Ts...>> operator()(const Convertible& value, Error& error) const {
std::string type = util::Interpolatable<T>::value ? "exponential" : "interval";
auto typeValue = objectMember(value, "type");
@@ -193,8 +187,7 @@ public:
template <class T>
struct Converter<CameraFunction<T>> {
- template <class V>
- optional<CameraFunction<T>> operator()(const V& value, Error& error) const {
+ optional<CameraFunction<T>> operator()(const Convertible& value, Error& error) const {
if (!isObject(value)) {
error = { "function must be an object" };
return {};
@@ -209,8 +202,8 @@ struct Converter<CameraFunction<T>> {
}
};
-template <class T, class V>
-optional<optional<T>> convertDefaultValue(const V& value, Error& error) {
+template <class T>
+optional<optional<T>> convertDefaultValue(const Convertible& value, Error& error) {
auto defaultValueValue = objectMember(value, "default");
if (!defaultValueValue) {
return optional<T>();
@@ -227,8 +220,7 @@ optional<optional<T>> convertDefaultValue(const V& value, Error& error) {
template <class T>
struct Converter<SourceFunction<T>> {
- template <class V>
- optional<SourceFunction<T>> operator()(const V& value, Error& error) const {
+ optional<SourceFunction<T>> operator()(const Convertible& value, Error& error) const {
if (!isObject(value)) {
error = { "function must be an object" };
return {};
@@ -267,8 +259,7 @@ struct CompositeValue : std::pair<float, S> {
template <class S>
struct Converter<CompositeValue<S>> {
- template <class V>
- optional<CompositeValue<S>> operator()(const V& value, Error& error) const {
+ optional<CompositeValue<S>> operator()(const Convertible& value, Error& error) const {
if (!isObject(value)) {
error = { "stop must be an object" };
return {};
@@ -304,8 +295,7 @@ template <class T>
struct Converter<CompositeExponentialStops<T>> {
static constexpr const char * type = "exponential";
- template <class V>
- optional<CompositeExponentialStops<T>> operator()(const V& value, Error& error) const {
+ optional<CompositeExponentialStops<T>> operator()(const Convertible& value, Error& error) const {
auto stops = convertStops<CompositeValue<float>, T>(value, error);
if (!stops) {
return {};
@@ -330,8 +320,7 @@ template <class T>
struct Converter<CompositeIntervalStops<T>> {
static constexpr const char * type = "interval";
- template <class V>
- optional<CompositeIntervalStops<T>> operator()(const V& value, Error& error) const {
+ optional<CompositeIntervalStops<T>> operator()(const Convertible& value, Error& error) const {
auto stops = convertStops<CompositeValue<float>, T>(value, error);
if (!stops) {
return {};
@@ -350,8 +339,7 @@ template <class T>
struct Converter<CompositeCategoricalStops<T>> {
static constexpr const char * type = "categorical";
- template <class V>
- optional<CompositeCategoricalStops<T>> operator()(const V& value, Error& error) const {
+ optional<CompositeCategoricalStops<T>> operator()(const Convertible& value, Error& error) const {
auto stops = convertStops<CompositeValue<CategoricalValue>, T>(value, error);
if (!stops) {
return {};
@@ -368,8 +356,7 @@ struct Converter<CompositeCategoricalStops<T>> {
template <class T>
struct Converter<CompositeFunction<T>> {
- template <class V>
- optional<CompositeFunction<T>> operator()(const V& value, Error& error) const {
+ optional<CompositeFunction<T>> operator()(const Convertible& value, Error& error) const {
if (!isObject(value)) {
error = { "function must be an object" };
return {};
diff --git a/include/mbgl/style/conversion/geojson.hpp b/include/mbgl/style/conversion/geojson.hpp
index 0b594f066c..403c5f953b 100644
--- a/include/mbgl/style/conversion/geojson.hpp
+++ b/include/mbgl/style/conversion/geojson.hpp
@@ -7,15 +7,13 @@ namespace mbgl {
namespace style {
namespace conversion {
+// Workaround until https://github.com/mapbox/mapbox-gl-native/issues/5623 is done.
+optional<GeoJSON> parseGeoJSON(const std::string&, Error&);
+
template <>
struct Converter<GeoJSON> {
public:
- optional<GeoJSON> operator()(const std::string&, Error&) const;
-
- // This is explicitly specialized in the .cpp file for JSValue. It may also be explicitly
- // specialized for SDK-specific types (e.g. mbgl::android::Value).
- template <class V>
- optional<GeoJSON> operator()(const V&, Error&) const;
+ optional<GeoJSON> operator()(const Convertible&, Error&) const;
};
} // namespace conversion
diff --git a/include/mbgl/style/conversion/geojson_options.hpp b/include/mbgl/style/conversion/geojson_options.hpp
index 1c9c18250c..3f625babb6 100644
--- a/include/mbgl/style/conversion/geojson_options.hpp
+++ b/include/mbgl/style/conversion/geojson_options.hpp
@@ -9,84 +9,7 @@ namespace conversion {
template <>
struct Converter<GeoJSONOptions> {
-
- template <class V>
- optional<GeoJSONOptions> operator()(const V& value, Error& error) const {
- GeoJSONOptions options;
-
- const auto minzoomValue = objectMember(value, "minzoom");
- if (minzoomValue) {
- if (toNumber(*minzoomValue)) {
- options.minzoom = static_cast<uint8_t>(*toNumber(*minzoomValue));
- } else {
- error = { "GeoJSON source minzoom value must be a number" };
- return {};
- }
- }
-
- const auto maxzoomValue = objectMember(value, "maxzoom");
- if (maxzoomValue) {
- if (toNumber(*maxzoomValue)) {
- options.maxzoom = static_cast<uint8_t>(*toNumber(*maxzoomValue));
- } else {
- error = { "GeoJSON source maxzoom value must be a number" };
- return {};
- }
- }
-
- const auto bufferValue = objectMember(value, "buffer");
- if (bufferValue) {
- if (toNumber(*bufferValue)) {
- options.buffer = static_cast<uint16_t>(*toNumber(*bufferValue));
- } else {
- error = { "GeoJSON source buffer value must be a number" };
- return {};
- }
- }
-
- const auto toleranceValue = objectMember(value, "tolerance");
- if (toleranceValue) {
- if (toNumber(*toleranceValue)) {
- options.tolerance = static_cast<double>(*toNumber(*toleranceValue));
- } else {
- error = { "GeoJSON source tolerance value must be a number" };
- return {};
- }
- }
-
- const auto clusterValue = objectMember(value, "cluster");
- if (clusterValue) {
- if (toBool(*clusterValue)) {
- options.cluster = *toBool(*clusterValue);
- } else {
- error = { "GeoJSON source cluster value must be a boolean" };
- return {};
- }
- }
-
- const auto clusterMaxZoomValue = objectMember(value, "clusterMaxZoom");
- if (clusterMaxZoomValue) {
- if (toNumber(*clusterMaxZoomValue)) {
- options.clusterMaxZoom = static_cast<uint8_t>(*toNumber(*clusterMaxZoomValue));
- } else {
- error = { "GeoJSON source clusterMaxZoom value must be a number" };
- return {};
- }
- }
-
- const auto clusterRadiusValue = objectMember(value, "clusterRadius");
- if (clusterRadiusValue) {
- if (toNumber(*clusterRadiusValue)) {
- options.clusterRadius = static_cast<double>(*toNumber(*clusterRadiusValue));
- } else {
- error = { "GeoJSON source clusterRadius value must be a number" };
- return {};
- }
- }
-
- return { options };
- }
-
+ optional<GeoJSONOptions> operator()(const Convertible& value, Error& error) const;
};
} // namespace conversion
diff --git a/include/mbgl/style/conversion/get_json_type.hpp b/include/mbgl/style/conversion/get_json_type.hpp
new file mode 100644
index 0000000000..f7efebccce
--- /dev/null
+++ b/include/mbgl/style/conversion/get_json_type.hpp
@@ -0,0 +1,14 @@
+#pragma once
+
+#include <mbgl/style/conversion.hpp>
+#include <string>
+
+namespace mbgl {
+namespace style {
+namespace conversion {
+
+std::string getJSONType(const Convertible& value);
+
+} // namespace conversion
+} // namespace style
+} // namespace mbgl
diff --git a/include/mbgl/style/conversion/layer.hpp b/include/mbgl/style/conversion/layer.hpp
index 1fe467165d..1c0e2e2f07 100644
--- a/include/mbgl/style/conversion/layer.hpp
+++ b/include/mbgl/style/conversion/layer.hpp
@@ -1,220 +1,24 @@
#pragma once
#include <mbgl/style/layer.hpp>
-#include <mbgl/style/layers/background_layer.hpp>
-#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/line_layer.hpp>
-#include <mbgl/style/layers/raster_layer.hpp>
-#include <mbgl/style/layers/symbol_layer.hpp>
#include <mbgl/style/conversion.hpp>
-#include <mbgl/style/conversion/constant.hpp>
-#include <mbgl/style/conversion/filter.hpp>
-#include <mbgl/style/conversion/make_property_setters.hpp>
+
+#include <memory>
namespace mbgl {
namespace style {
namespace conversion {
-template <class V>
-optional<Error> setLayoutProperty(Layer& layer, const std::string& name, const V& value) {
- static const auto setters = makeLayoutPropertySetters<V>();
- auto it = setters.find(name);
- if (it == setters.end()) {
- return Error { "property not found" };
- }
- return it->second(layer, value);
-}
-
-template <class V>
-optional<Error> setPaintProperty(Layer& layer, const std::string& name, const V& value) {
- static const auto setters = makePaintPropertySetters<V>();
- auto it = setters.find(name);
- if (it == setters.end()) {
- return Error { "property not found" };
- }
- return it->second(layer, value);
-}
-
-template <class V>
-optional<Error> setPaintProperties(Layer& layer, const V& value) {
- auto paintValue = objectMember(value, "paint");
- if (!paintValue) {
- return {};
- }
- return eachMember(*paintValue, [&] (const std::string& k, const V& v) {
- return setPaintProperty(layer, k, v);
- });
-}
-
template <>
struct Converter<std::unique_ptr<Layer>> {
public:
- template <class V>
- optional<std::unique_ptr<Layer>> operator()(const V& value, Error& error) const {
- if (!isObject(value)) {
- error = { "layer must be an object" };
- return {};
- }
-
- auto idValue = objectMember(value, "id");
- if (!idValue) {
- error = { "layer must have an id" };
- return {};
- }
-
- optional<std::string> id = toString(*idValue);
- if (!id) {
- error = { "layer id must be a string" };
- return {};
- }
-
- auto typeValue = objectMember(value, "type");
- if (!typeValue) {
- error = { "layer must have a type" };
- return {};
- }
-
- optional<std::string> type = toString(*typeValue);
- if (!type) {
- error = { "layer type must be a string" };
- return {};
- }
-
- optional<std::unique_ptr<Layer>> converted;
-
- if (*type == "fill") {
- converted = convertVectorLayer<FillLayer>(*id, value, error);
- } else if (*type == "fill-extrusion") {
- converted = convertVectorLayer<FillExtrusionLayer>(*id, value, error);
- } else if (*type == "line") {
- converted = convertVectorLayer<LineLayer>(*id, value, error);
- } else if (*type == "circle") {
- converted = convertVectorLayer<CircleLayer>(*id, value, error);
- } else if (*type == "symbol") {
- converted = convertVectorLayer<SymbolLayer>(*id, value, error);
- } else if (*type == "raster") {
- converted = convertRasterLayer(*id, value, error);
- } else if (*type == "background") {
- converted = convertBackgroundLayer(*id, value, error);
- } else {
- error = { "invalid layer type" };
- return {};
- }
-
- if (!converted) {
- return converted;
- }
-
- std::unique_ptr<Layer> layer = std::move(*converted);
-
- auto minzoomValue = objectMember(value, "minzoom");
- if (minzoomValue) {
- optional<float> minzoom = toNumber(*minzoomValue);
- if (!minzoom) {
- error = { "minzoom must be numeric" };
- return {};
- }
- layer->setMinZoom(*minzoom);
- }
-
- auto maxzoomValue = objectMember(value, "maxzoom");
- if (maxzoomValue) {
- optional<float> maxzoom = toNumber(*maxzoomValue);
- if (!maxzoom) {
- error = { "maxzoom must be numeric" };
- return {};
- }
- layer->setMaxZoom(*maxzoom);
- }
-
- auto layoutValue = objectMember(value, "layout");
- if (layoutValue) {
- if (!isObject(*layoutValue)) {
- error = { "layout must be an object" };
- return {};
- }
- optional<Error> error_ = eachMember(*layoutValue, [&] (const std::string& k, const V& v) {
- return setLayoutProperty(*layer, k, v);
- });
- if (error_) {
- error = *error_;
- return {};
- }
- }
-
- optional<Error> error_ = setPaintProperties(*layer, value);
- if (error_) {
- error = *error_;
- return {};
- }
-
- return std::move(layer);
- }
-
-private:
- template <class LayerType, class V>
- optional<std::unique_ptr<Layer>> convertVectorLayer(const std::string& id, const V& value, Error& error) const {
- auto sourceValue = objectMember(value, "source");
- if (!sourceValue) {
- error = { "layer must have a source" };
- return {};
- }
-
- optional<std::string> source = toString(*sourceValue);
- if (!source) {
- error = { "layer source must be a string" };
- return {};
- }
-
- std::unique_ptr<LayerType> layer = std::make_unique<LayerType>(id, *source);
-
- auto sourceLayerValue = objectMember(value, "source-layer");
- if (sourceLayerValue) {
- optional<std::string> sourceLayer = toString(*sourceLayerValue);
- if (!sourceLayer) {
- error = { "layer source-layer must be a string" };
- return {};
- }
- layer->setSourceLayer(*sourceLayer);
- }
-
- auto filterValue = objectMember(value, "filter");
- if (filterValue) {
- optional<Filter> filter = convert<Filter>(*filterValue, error);
- if (!filter) {
- return {};
- }
- layer->setFilter(*filter);
- }
-
- return { std::move(layer) };
- }
-
- template <class V>
- optional<std::unique_ptr<Layer>> convertRasterLayer(const std::string& id, const V& value, Error& error) const {
- auto sourceValue = objectMember(value, "source");
- if (!sourceValue) {
- error = { "layer must have a source" };
- return {};
- }
-
- optional<std::string> source = toString(*sourceValue);
- if (!source) {
- error = { "layer source must be a string" };
- return {};
- }
-
- return { std::make_unique<RasterLayer>(id, *source) };
- }
-
- template <class V>
- optional<std::unique_ptr<Layer>> convertBackgroundLayer(const std::string& id, const V&, Error&) const {
- return { std::make_unique<BackgroundLayer>(id) };
- }
+ optional<std::unique_ptr<Layer>> operator()(const Convertible& value, Error& error) const;
};
+optional<Error> setLayoutProperty(Layer& layer, const std::string& name, const Convertible& value);
+optional<Error> setPaintProperty(Layer& layer, const std::string& name, const Convertible& value);
+optional<Error> setPaintProperties(Layer& layer, const Convertible& value);
+
} // namespace conversion
} // namespace style
} // namespace mbgl
diff --git a/include/mbgl/style/conversion/light.hpp b/include/mbgl/style/conversion/light.hpp
index ba162516c0..289fca2e31 100644
--- a/include/mbgl/style/conversion/light.hpp
+++ b/include/mbgl/style/conversion/light.hpp
@@ -2,9 +2,6 @@
#include <mbgl/style/light.hpp>
#include <mbgl/style/conversion.hpp>
-#include <mbgl/style/conversion/position.hpp>
-#include <mbgl/style/conversion/property_value.hpp>
-#include <mbgl/style/conversion/transition_options.hpp>
namespace mbgl {
namespace style {
@@ -13,108 +10,7 @@ namespace conversion {
template <>
struct Converter<Light> {
public:
- template <class V>
- optional<Light> operator()(const V& value, Error& error) const {
- if (!isObject(value)) {
- error = { "light must be an object" };
- return {};
- }
-
- Light light;
-
- const auto anchor = objectMember(value, "anchor");
- if (anchor) {
- optional<PropertyValue<LightAnchorType>> convertedAnchor =
- convert<PropertyValue<LightAnchorType>>(*anchor, error);
-
- if (convertedAnchor) {
- light.setAnchor(*convertedAnchor);
- } else {
- return {};
- }
- }
-
- const auto anchorTransition = objectMember(value, "anchor-transition");
- if (anchorTransition) {
- optional<TransitionOptions> transition =
- convert<TransitionOptions>(*anchorTransition, error);
- if (transition) {
- light.setAnchorTransition(*transition);
- } else {
- return {};
- }
- }
-
- const auto color = objectMember(value, "color");
- if (color) {
- optional<PropertyValue<Color>> convertedColor =
- convert<PropertyValue<Color>>(*color, error);
-
- if (convertedColor) {
- light.setColor(*convertedColor);
- } else {
- return {};
- }
- }
-
- const auto colorTransition = objectMember(value, "color-transition");
- if (colorTransition) {
- optional<TransitionOptions> transition =
- convert<TransitionOptions>(*colorTransition, error);
- if (transition) {
- light.setColorTransition(*transition);
- } else {
- return {};
- }
- }
-
- const auto position = objectMember(value, "position");
- if (position) {
- optional<PropertyValue<Position>> convertedPosition =
- convert<PropertyValue<Position>>(*position, error);
-
- if (convertedPosition) {
- light.setPosition(*convertedPosition);
- } else {
- return {};
- }
- }
-
- const auto positionTransition = objectMember(value, "position-transition");
- if (positionTransition) {
- optional<TransitionOptions> transition =
- convert<TransitionOptions>(*positionTransition, error);
- if (transition) {
- light.setPositionTransition(*transition);
- } else {
- return {};
- }
- }
-
- const auto intensity = objectMember(value, "intensity");
- if (intensity) {
- optional<PropertyValue<float>> convertedIntensity =
- convert<PropertyValue<float>>(*intensity, error);
-
- if (convertedIntensity) {
- light.setIntensity(*convertedIntensity);
- } else {
- return {};
- }
- }
-
- const auto intensityTransition = objectMember(value, "intensity-transition");
- if (intensityTransition) {
- optional<TransitionOptions> transition =
- convert<TransitionOptions>(*intensityTransition, error);
- if (transition) {
- light.setIntensityTransition(*transition);
- } else {
- return {};
- }
- }
- return { std::move(light) };
- };
+ optional<Light> operator()(const Convertible& value, Error& error) const;
};
} // namespace conversion
diff --git a/include/mbgl/style/conversion/make_property_setters.hpp b/include/mbgl/style/conversion/make_property_setters.hpp
deleted file mode 100644
index 59b0e7be32..0000000000
--- a/include/mbgl/style/conversion/make_property_setters.hpp
+++ /dev/null
@@ -1,211 +0,0 @@
-#pragma once
-
-// This file is generated. Edit make_property_setters.hpp.ejs, then run `make style-code`.
-
-#include <mbgl/style/conversion/property_setter.hpp>
-
-#include <mbgl/style/layers/fill_layer.hpp>
-#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/fill_extrusion_layer.hpp>
-#include <mbgl/style/layers/raster_layer.hpp>
-#include <mbgl/style/layers/background_layer.hpp>
-
-#include <unordered_map>
-
-namespace mbgl {
-namespace style {
-namespace conversion {
-
-template <class V>
-auto makeLayoutPropertySetters() {
- std::unordered_map<std::string, PropertySetter<V>> result;
-
- result["visibility"] = &setVisibility<V>;
-
-
- result["line-cap"] = &setProperty<V, LineLayer, PropertyValue<LineCapType>, &LineLayer::setLineCap>;
- result["line-join"] = &setProperty<V, LineLayer, DataDrivenPropertyValue<LineJoinType>, &LineLayer::setLineJoin>;
- result["line-miter-limit"] = &setProperty<V, LineLayer, PropertyValue<float>, &LineLayer::setLineMiterLimit>;
- result["line-round-limit"] = &setProperty<V, LineLayer, PropertyValue<float>, &LineLayer::setLineRoundLimit>;
-
- result["symbol-placement"] = &setProperty<V, SymbolLayer, PropertyValue<SymbolPlacementType>, &SymbolLayer::setSymbolPlacement>;
- result["symbol-spacing"] = &setProperty<V, SymbolLayer, PropertyValue<float>, &SymbolLayer::setSymbolSpacing>;
- result["symbol-avoid-edges"] = &setProperty<V, SymbolLayer, PropertyValue<bool>, &SymbolLayer::setSymbolAvoidEdges>;
- result["icon-allow-overlap"] = &setProperty<V, SymbolLayer, PropertyValue<bool>, &SymbolLayer::setIconAllowOverlap>;
- result["icon-ignore-placement"] = &setProperty<V, SymbolLayer, PropertyValue<bool>, &SymbolLayer::setIconIgnorePlacement>;
- result["icon-optional"] = &setProperty<V, SymbolLayer, PropertyValue<bool>, &SymbolLayer::setIconOptional>;
- result["icon-rotation-alignment"] = &setProperty<V, SymbolLayer, PropertyValue<AlignmentType>, &SymbolLayer::setIconRotationAlignment>;
- result["icon-size"] = &setProperty<V, SymbolLayer, DataDrivenPropertyValue<float>, &SymbolLayer::setIconSize>;
- result["icon-text-fit"] = &setProperty<V, SymbolLayer, PropertyValue<IconTextFitType>, &SymbolLayer::setIconTextFit>;
- result["icon-text-fit-padding"] = &setProperty<V, SymbolLayer, PropertyValue<std::array<float, 4>>, &SymbolLayer::setIconTextFitPadding>;
- result["icon-image"] = &setProperty<V, SymbolLayer, DataDrivenPropertyValue<std::string>, &SymbolLayer::setIconImage>;
- result["icon-rotate"] = &setProperty<V, SymbolLayer, DataDrivenPropertyValue<float>, &SymbolLayer::setIconRotate>;
- result["icon-padding"] = &setProperty<V, SymbolLayer, PropertyValue<float>, &SymbolLayer::setIconPadding>;
- result["icon-keep-upright"] = &setProperty<V, SymbolLayer, PropertyValue<bool>, &SymbolLayer::setIconKeepUpright>;
- result["icon-offset"] = &setProperty<V, SymbolLayer, DataDrivenPropertyValue<std::array<float, 2>>, &SymbolLayer::setIconOffset>;
- result["icon-anchor"] = &setProperty<V, SymbolLayer, DataDrivenPropertyValue<SymbolAnchorType>, &SymbolLayer::setIconAnchor>;
- result["icon-pitch-alignment"] = &setProperty<V, SymbolLayer, PropertyValue<AlignmentType>, &SymbolLayer::setIconPitchAlignment>;
- result["text-pitch-alignment"] = &setProperty<V, SymbolLayer, PropertyValue<AlignmentType>, &SymbolLayer::setTextPitchAlignment>;
- result["text-rotation-alignment"] = &setProperty<V, SymbolLayer, PropertyValue<AlignmentType>, &SymbolLayer::setTextRotationAlignment>;
- result["text-field"] = &setProperty<V, SymbolLayer, DataDrivenPropertyValue<std::string>, &SymbolLayer::setTextField>;
- result["text-font"] = &setProperty<V, SymbolLayer, PropertyValue<std::vector<std::string>>, &SymbolLayer::setTextFont>;
- result["text-size"] = &setProperty<V, SymbolLayer, DataDrivenPropertyValue<float>, &SymbolLayer::setTextSize>;
- result["text-max-width"] = &setProperty<V, SymbolLayer, DataDrivenPropertyValue<float>, &SymbolLayer::setTextMaxWidth>;
- result["text-line-height"] = &setProperty<V, SymbolLayer, PropertyValue<float>, &SymbolLayer::setTextLineHeight>;
- result["text-letter-spacing"] = &setProperty<V, SymbolLayer, DataDrivenPropertyValue<float>, &SymbolLayer::setTextLetterSpacing>;
- result["text-justify"] = &setProperty<V, SymbolLayer, DataDrivenPropertyValue<TextJustifyType>, &SymbolLayer::setTextJustify>;
- result["text-anchor"] = &setProperty<V, SymbolLayer, DataDrivenPropertyValue<SymbolAnchorType>, &SymbolLayer::setTextAnchor>;
- result["text-max-angle"] = &setProperty<V, SymbolLayer, PropertyValue<float>, &SymbolLayer::setTextMaxAngle>;
- result["text-rotate"] = &setProperty<V, SymbolLayer, DataDrivenPropertyValue<float>, &SymbolLayer::setTextRotate>;
- result["text-padding"] = &setProperty<V, SymbolLayer, PropertyValue<float>, &SymbolLayer::setTextPadding>;
- result["text-keep-upright"] = &setProperty<V, SymbolLayer, PropertyValue<bool>, &SymbolLayer::setTextKeepUpright>;
- result["text-transform"] = &setProperty<V, SymbolLayer, DataDrivenPropertyValue<TextTransformType>, &SymbolLayer::setTextTransform>;
- result["text-offset"] = &setProperty<V, SymbolLayer, DataDrivenPropertyValue<std::array<float, 2>>, &SymbolLayer::setTextOffset>;
- result["text-allow-overlap"] = &setProperty<V, SymbolLayer, PropertyValue<bool>, &SymbolLayer::setTextAllowOverlap>;
- result["text-ignore-placement"] = &setProperty<V, SymbolLayer, PropertyValue<bool>, &SymbolLayer::setTextIgnorePlacement>;
- result["text-optional"] = &setProperty<V, SymbolLayer, PropertyValue<bool>, &SymbolLayer::setTextOptional>;
-
-
-
-
-
- return result;
-}
-
-template <class V>
-auto makePaintPropertySetters() {
- std::unordered_map<std::string, PropertySetter<V>> result;
-
- result["fill-antialias"] = &setProperty<V, FillLayer, PropertyValue<bool>, &FillLayer::setFillAntialias>;
- result["fill-antialias-transition"] = &setTransition<V, FillLayer, &FillLayer::setFillAntialiasTransition>;
- result["fill-opacity"] = &setProperty<V, FillLayer, DataDrivenPropertyValue<float>, &FillLayer::setFillOpacity>;
- result["fill-opacity-transition"] = &setTransition<V, FillLayer, &FillLayer::setFillOpacityTransition>;
- result["fill-color"] = &setProperty<V, FillLayer, DataDrivenPropertyValue<Color>, &FillLayer::setFillColor>;
- result["fill-color-transition"] = &setTransition<V, FillLayer, &FillLayer::setFillColorTransition>;
- result["fill-outline-color"] = &setProperty<V, FillLayer, DataDrivenPropertyValue<Color>, &FillLayer::setFillOutlineColor>;
- result["fill-outline-color-transition"] = &setTransition<V, FillLayer, &FillLayer::setFillOutlineColorTransition>;
- result["fill-translate"] = &setProperty<V, FillLayer, PropertyValue<std::array<float, 2>>, &FillLayer::setFillTranslate>;
- result["fill-translate-transition"] = &setTransition<V, FillLayer, &FillLayer::setFillTranslateTransition>;
- result["fill-translate-anchor"] = &setProperty<V, FillLayer, PropertyValue<TranslateAnchorType>, &FillLayer::setFillTranslateAnchor>;
- result["fill-translate-anchor-transition"] = &setTransition<V, FillLayer, &FillLayer::setFillTranslateAnchorTransition>;
- result["fill-pattern"] = &setProperty<V, FillLayer, PropertyValue<std::string>, &FillLayer::setFillPattern>;
- result["fill-pattern-transition"] = &setTransition<V, FillLayer, &FillLayer::setFillPatternTransition>;
-
- result["line-opacity"] = &setProperty<V, LineLayer, DataDrivenPropertyValue<float>, &LineLayer::setLineOpacity>;
- result["line-opacity-transition"] = &setTransition<V, LineLayer, &LineLayer::setLineOpacityTransition>;
- result["line-color"] = &setProperty<V, LineLayer, DataDrivenPropertyValue<Color>, &LineLayer::setLineColor>;
- result["line-color-transition"] = &setTransition<V, LineLayer, &LineLayer::setLineColorTransition>;
- result["line-translate"] = &setProperty<V, LineLayer, PropertyValue<std::array<float, 2>>, &LineLayer::setLineTranslate>;
- result["line-translate-transition"] = &setTransition<V, LineLayer, &LineLayer::setLineTranslateTransition>;
- result["line-translate-anchor"] = &setProperty<V, LineLayer, PropertyValue<TranslateAnchorType>, &LineLayer::setLineTranslateAnchor>;
- result["line-translate-anchor-transition"] = &setTransition<V, LineLayer, &LineLayer::setLineTranslateAnchorTransition>;
- result["line-width"] = &setProperty<V, LineLayer, DataDrivenPropertyValue<float>, &LineLayer::setLineWidth>;
- result["line-width-transition"] = &setTransition<V, LineLayer, &LineLayer::setLineWidthTransition>;
- result["line-gap-width"] = &setProperty<V, LineLayer, DataDrivenPropertyValue<float>, &LineLayer::setLineGapWidth>;
- result["line-gap-width-transition"] = &setTransition<V, LineLayer, &LineLayer::setLineGapWidthTransition>;
- result["line-offset"] = &setProperty<V, LineLayer, DataDrivenPropertyValue<float>, &LineLayer::setLineOffset>;
- result["line-offset-transition"] = &setTransition<V, LineLayer, &LineLayer::setLineOffsetTransition>;
- result["line-blur"] = &setProperty<V, LineLayer, DataDrivenPropertyValue<float>, &LineLayer::setLineBlur>;
- result["line-blur-transition"] = &setTransition<V, LineLayer, &LineLayer::setLineBlurTransition>;
- result["line-dasharray"] = &setProperty<V, LineLayer, PropertyValue<std::vector<float>>, &LineLayer::setLineDasharray>;
- result["line-dasharray-transition"] = &setTransition<V, LineLayer, &LineLayer::setLineDasharrayTransition>;
- result["line-pattern"] = &setProperty<V, LineLayer, PropertyValue<std::string>, &LineLayer::setLinePattern>;
- result["line-pattern-transition"] = &setTransition<V, LineLayer, &LineLayer::setLinePatternTransition>;
-
- result["icon-opacity"] = &setProperty<V, SymbolLayer, DataDrivenPropertyValue<float>, &SymbolLayer::setIconOpacity>;
- result["icon-opacity-transition"] = &setTransition<V, SymbolLayer, &SymbolLayer::setIconOpacityTransition>;
- result["icon-color"] = &setProperty<V, SymbolLayer, DataDrivenPropertyValue<Color>, &SymbolLayer::setIconColor>;
- result["icon-color-transition"] = &setTransition<V, SymbolLayer, &SymbolLayer::setIconColorTransition>;
- result["icon-halo-color"] = &setProperty<V, SymbolLayer, DataDrivenPropertyValue<Color>, &SymbolLayer::setIconHaloColor>;
- result["icon-halo-color-transition"] = &setTransition<V, SymbolLayer, &SymbolLayer::setIconHaloColorTransition>;
- result["icon-halo-width"] = &setProperty<V, SymbolLayer, DataDrivenPropertyValue<float>, &SymbolLayer::setIconHaloWidth>;
- result["icon-halo-width-transition"] = &setTransition<V, SymbolLayer, &SymbolLayer::setIconHaloWidthTransition>;
- result["icon-halo-blur"] = &setProperty<V, SymbolLayer, DataDrivenPropertyValue<float>, &SymbolLayer::setIconHaloBlur>;
- result["icon-halo-blur-transition"] = &setTransition<V, SymbolLayer, &SymbolLayer::setIconHaloBlurTransition>;
- result["icon-translate"] = &setProperty<V, SymbolLayer, PropertyValue<std::array<float, 2>>, &SymbolLayer::setIconTranslate>;
- result["icon-translate-transition"] = &setTransition<V, SymbolLayer, &SymbolLayer::setIconTranslateTransition>;
- result["icon-translate-anchor"] = &setProperty<V, SymbolLayer, PropertyValue<TranslateAnchorType>, &SymbolLayer::setIconTranslateAnchor>;
- result["icon-translate-anchor-transition"] = &setTransition<V, SymbolLayer, &SymbolLayer::setIconTranslateAnchorTransition>;
- result["text-opacity"] = &setProperty<V, SymbolLayer, DataDrivenPropertyValue<float>, &SymbolLayer::setTextOpacity>;
- result["text-opacity-transition"] = &setTransition<V, SymbolLayer, &SymbolLayer::setTextOpacityTransition>;
- result["text-color"] = &setProperty<V, SymbolLayer, DataDrivenPropertyValue<Color>, &SymbolLayer::setTextColor>;
- result["text-color-transition"] = &setTransition<V, SymbolLayer, &SymbolLayer::setTextColorTransition>;
- result["text-halo-color"] = &setProperty<V, SymbolLayer, DataDrivenPropertyValue<Color>, &SymbolLayer::setTextHaloColor>;
- result["text-halo-color-transition"] = &setTransition<V, SymbolLayer, &SymbolLayer::setTextHaloColorTransition>;
- result["text-halo-width"] = &setProperty<V, SymbolLayer, DataDrivenPropertyValue<float>, &SymbolLayer::setTextHaloWidth>;
- result["text-halo-width-transition"] = &setTransition<V, SymbolLayer, &SymbolLayer::setTextHaloWidthTransition>;
- result["text-halo-blur"] = &setProperty<V, SymbolLayer, DataDrivenPropertyValue<float>, &SymbolLayer::setTextHaloBlur>;
- result["text-halo-blur-transition"] = &setTransition<V, SymbolLayer, &SymbolLayer::setTextHaloBlurTransition>;
- result["text-translate"] = &setProperty<V, SymbolLayer, PropertyValue<std::array<float, 2>>, &SymbolLayer::setTextTranslate>;
- result["text-translate-transition"] = &setTransition<V, SymbolLayer, &SymbolLayer::setTextTranslateTransition>;
- result["text-translate-anchor"] = &setProperty<V, SymbolLayer, PropertyValue<TranslateAnchorType>, &SymbolLayer::setTextTranslateAnchor>;
- result["text-translate-anchor-transition"] = &setTransition<V, SymbolLayer, &SymbolLayer::setTextTranslateAnchorTransition>;
-
- result["circle-radius"] = &setProperty<V, CircleLayer, DataDrivenPropertyValue<float>, &CircleLayer::setCircleRadius>;
- result["circle-radius-transition"] = &setTransition<V, CircleLayer, &CircleLayer::setCircleRadiusTransition>;
- result["circle-color"] = &setProperty<V, CircleLayer, DataDrivenPropertyValue<Color>, &CircleLayer::setCircleColor>;
- result["circle-color-transition"] = &setTransition<V, CircleLayer, &CircleLayer::setCircleColorTransition>;
- result["circle-blur"] = &setProperty<V, CircleLayer, DataDrivenPropertyValue<float>, &CircleLayer::setCircleBlur>;
- result["circle-blur-transition"] = &setTransition<V, CircleLayer, &CircleLayer::setCircleBlurTransition>;
- result["circle-opacity"] = &setProperty<V, CircleLayer, DataDrivenPropertyValue<float>, &CircleLayer::setCircleOpacity>;
- result["circle-opacity-transition"] = &setTransition<V, CircleLayer, &CircleLayer::setCircleOpacityTransition>;
- result["circle-translate"] = &setProperty<V, CircleLayer, PropertyValue<std::array<float, 2>>, &CircleLayer::setCircleTranslate>;
- result["circle-translate-transition"] = &setTransition<V, CircleLayer, &CircleLayer::setCircleTranslateTransition>;
- result["circle-translate-anchor"] = &setProperty<V, CircleLayer, PropertyValue<TranslateAnchorType>, &CircleLayer::setCircleTranslateAnchor>;
- result["circle-translate-anchor-transition"] = &setTransition<V, CircleLayer, &CircleLayer::setCircleTranslateAnchorTransition>;
- result["circle-pitch-scale"] = &setProperty<V, CircleLayer, PropertyValue<CirclePitchScaleType>, &CircleLayer::setCirclePitchScale>;
- result["circle-pitch-scale-transition"] = &setTransition<V, CircleLayer, &CircleLayer::setCirclePitchScaleTransition>;
- result["circle-pitch-alignment"] = &setProperty<V, CircleLayer, PropertyValue<AlignmentType>, &CircleLayer::setCirclePitchAlignment>;
- result["circle-pitch-alignment-transition"] = &setTransition<V, CircleLayer, &CircleLayer::setCirclePitchAlignmentTransition>;
- result["circle-stroke-width"] = &setProperty<V, CircleLayer, DataDrivenPropertyValue<float>, &CircleLayer::setCircleStrokeWidth>;
- result["circle-stroke-width-transition"] = &setTransition<V, CircleLayer, &CircleLayer::setCircleStrokeWidthTransition>;
- result["circle-stroke-color"] = &setProperty<V, CircleLayer, DataDrivenPropertyValue<Color>, &CircleLayer::setCircleStrokeColor>;
- result["circle-stroke-color-transition"] = &setTransition<V, CircleLayer, &CircleLayer::setCircleStrokeColorTransition>;
- result["circle-stroke-opacity"] = &setProperty<V, CircleLayer, DataDrivenPropertyValue<float>, &CircleLayer::setCircleStrokeOpacity>;
- result["circle-stroke-opacity-transition"] = &setTransition<V, CircleLayer, &CircleLayer::setCircleStrokeOpacityTransition>;
-
- result["fill-extrusion-opacity"] = &setProperty<V, FillExtrusionLayer, PropertyValue<float>, &FillExtrusionLayer::setFillExtrusionOpacity>;
- result["fill-extrusion-opacity-transition"] = &setTransition<V, FillExtrusionLayer, &FillExtrusionLayer::setFillExtrusionOpacityTransition>;
- result["fill-extrusion-color"] = &setProperty<V, FillExtrusionLayer, DataDrivenPropertyValue<Color>, &FillExtrusionLayer::setFillExtrusionColor>;
- result["fill-extrusion-color-transition"] = &setTransition<V, FillExtrusionLayer, &FillExtrusionLayer::setFillExtrusionColorTransition>;
- result["fill-extrusion-translate"] = &setProperty<V, FillExtrusionLayer, PropertyValue<std::array<float, 2>>, &FillExtrusionLayer::setFillExtrusionTranslate>;
- result["fill-extrusion-translate-transition"] = &setTransition<V, FillExtrusionLayer, &FillExtrusionLayer::setFillExtrusionTranslateTransition>;
- result["fill-extrusion-translate-anchor"] = &setProperty<V, FillExtrusionLayer, PropertyValue<TranslateAnchorType>, &FillExtrusionLayer::setFillExtrusionTranslateAnchor>;
- result["fill-extrusion-translate-anchor-transition"] = &setTransition<V, FillExtrusionLayer, &FillExtrusionLayer::setFillExtrusionTranslateAnchorTransition>;
- result["fill-extrusion-pattern"] = &setProperty<V, FillExtrusionLayer, PropertyValue<std::string>, &FillExtrusionLayer::setFillExtrusionPattern>;
- result["fill-extrusion-pattern-transition"] = &setTransition<V, FillExtrusionLayer, &FillExtrusionLayer::setFillExtrusionPatternTransition>;
- result["fill-extrusion-height"] = &setProperty<V, FillExtrusionLayer, DataDrivenPropertyValue<float>, &FillExtrusionLayer::setFillExtrusionHeight>;
- result["fill-extrusion-height-transition"] = &setTransition<V, FillExtrusionLayer, &FillExtrusionLayer::setFillExtrusionHeightTransition>;
- result["fill-extrusion-base"] = &setProperty<V, FillExtrusionLayer, DataDrivenPropertyValue<float>, &FillExtrusionLayer::setFillExtrusionBase>;
- result["fill-extrusion-base-transition"] = &setTransition<V, FillExtrusionLayer, &FillExtrusionLayer::setFillExtrusionBaseTransition>;
-
- result["raster-opacity"] = &setProperty<V, RasterLayer, PropertyValue<float>, &RasterLayer::setRasterOpacity>;
- result["raster-opacity-transition"] = &setTransition<V, RasterLayer, &RasterLayer::setRasterOpacityTransition>;
- result["raster-hue-rotate"] = &setProperty<V, RasterLayer, PropertyValue<float>, &RasterLayer::setRasterHueRotate>;
- result["raster-hue-rotate-transition"] = &setTransition<V, RasterLayer, &RasterLayer::setRasterHueRotateTransition>;
- result["raster-brightness-min"] = &setProperty<V, RasterLayer, PropertyValue<float>, &RasterLayer::setRasterBrightnessMin>;
- result["raster-brightness-min-transition"] = &setTransition<V, RasterLayer, &RasterLayer::setRasterBrightnessMinTransition>;
- result["raster-brightness-max"] = &setProperty<V, RasterLayer, PropertyValue<float>, &RasterLayer::setRasterBrightnessMax>;
- result["raster-brightness-max-transition"] = &setTransition<V, RasterLayer, &RasterLayer::setRasterBrightnessMaxTransition>;
- result["raster-saturation"] = &setProperty<V, RasterLayer, PropertyValue<float>, &RasterLayer::setRasterSaturation>;
- result["raster-saturation-transition"] = &setTransition<V, RasterLayer, &RasterLayer::setRasterSaturationTransition>;
- result["raster-contrast"] = &setProperty<V, RasterLayer, PropertyValue<float>, &RasterLayer::setRasterContrast>;
- result["raster-contrast-transition"] = &setTransition<V, RasterLayer, &RasterLayer::setRasterContrastTransition>;
- result["raster-fade-duration"] = &setProperty<V, RasterLayer, PropertyValue<float>, &RasterLayer::setRasterFadeDuration>;
- result["raster-fade-duration-transition"] = &setTransition<V, RasterLayer, &RasterLayer::setRasterFadeDurationTransition>;
-
- result["background-color"] = &setProperty<V, BackgroundLayer, PropertyValue<Color>, &BackgroundLayer::setBackgroundColor>;
- result["background-color-transition"] = &setTransition<V, BackgroundLayer, &BackgroundLayer::setBackgroundColorTransition>;
- result["background-pattern"] = &setProperty<V, BackgroundLayer, PropertyValue<std::string>, &BackgroundLayer::setBackgroundPattern>;
- result["background-pattern-transition"] = &setTransition<V, BackgroundLayer, &BackgroundLayer::setBackgroundPatternTransition>;
- result["background-opacity"] = &setProperty<V, BackgroundLayer, PropertyValue<float>, &BackgroundLayer::setBackgroundOpacity>;
- result["background-opacity-transition"] = &setTransition<V, BackgroundLayer, &BackgroundLayer::setBackgroundOpacityTransition>;
-
- return result;
-}
-
-} // namespace conversion
-} // namespace style
-} // namespace mbgl
diff --git a/include/mbgl/style/conversion/make_property_setters.hpp.ejs b/include/mbgl/style/conversion/make_property_setters.hpp.ejs
deleted file mode 100644
index 19c9f70538..0000000000
--- a/include/mbgl/style/conversion/make_property_setters.hpp.ejs
+++ /dev/null
@@ -1,48 +0,0 @@
-#pragma once
-
-// This file is generated. Edit make_property_setters.hpp.ejs, then run `make style-code`.
-
-#include <mbgl/style/conversion/property_setter.hpp>
-
-<% for (const layer of locals.layers) { -%>
-#include <mbgl/style/layers/<%- layer.type.replace('-', '_') %>_layer.hpp>
-<% } -%>
-
-#include <unordered_map>
-
-namespace mbgl {
-namespace style {
-namespace conversion {
-
-template <class V>
-auto makeLayoutPropertySetters() {
- std::unordered_map<std::string, PropertySetter<V>> result;
-
- result["visibility"] = &setVisibility<V>;
-
-<% for (const layer of locals.layers) { -%>
-<% for (const property of layer.layoutProperties) { -%>
- result["<%- property.name %>"] = &setProperty<V, <%- camelize(layer.type) %>Layer, <%- propertyValueType(property) %>, &<%- camelize(layer.type) %>Layer::set<%- camelize(property.name) %>>;
-<% } -%>
-
-<% } -%>
- return result;
-}
-
-template <class V>
-auto makePaintPropertySetters() {
- std::unordered_map<std::string, PropertySetter<V>> result;
-
-<% for (const layer of locals.layers) { -%>
-<% for (const property of layer.paintProperties) { -%>
- result["<%- property.name %>"] = &setProperty<V, <%- camelize(layer.type) %>Layer, <%- propertyValueType(property) %>, &<%- camelize(layer.type) %>Layer::set<%- camelize(property.name) %>>;
- result["<%- property.name %>-transition"] = &setTransition<V, <%- camelize(layer.type) %>Layer, &<%- camelize(layer.type) %>Layer::set<%- camelize(property.name) %>Transition>;
-<% } -%>
-
-<% } -%>
- return result;
-}
-
-} // namespace conversion
-} // namespace style
-} // namespace mbgl
diff --git a/include/mbgl/style/conversion/position.hpp b/include/mbgl/style/conversion/position.hpp
index 7036b03822..044c45862d 100644
--- a/include/mbgl/style/conversion/position.hpp
+++ b/include/mbgl/style/conversion/position.hpp
@@ -2,9 +2,6 @@
#include <mbgl/style/conversion.hpp>
#include <mbgl/style/position.hpp>
-#include <mbgl/util/optional.hpp>
-
-#include <array>
namespace mbgl {
namespace style {
@@ -12,16 +9,7 @@ namespace conversion {
template <>
struct Converter<Position> {
- template <class V>
- optional<Position> operator()(const V& value, Error& error) const {
- optional<std::array<float, 3>> spherical = convert<std::array<float, 3>>(value, error);
-
- if (!spherical) {
- return {};
- }
-
- return Position(*spherical);
- }
+ optional<Position> operator()(const Convertible& value, Error& error) const;
};
} // namespace conversion
diff --git a/include/mbgl/style/conversion/property_value.hpp b/include/mbgl/style/conversion/property_value.hpp
index f8937da07d..97117de2ec 100644
--- a/include/mbgl/style/conversion/property_value.hpp
+++ b/include/mbgl/style/conversion/property_value.hpp
@@ -4,6 +4,11 @@
#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 {
@@ -11,10 +16,20 @@ namespace conversion {
template <class T>
struct Converter<PropertyValue<T>> {
- template <class V>
- optional<PropertyValue<T>> operator()(const V& value, Error& error) const {
+ optional<PropertyValue<T>> operator()(const Convertible& value, Error& error) const {
if (isUndefined(value)) {
return PropertyValue<T>();
+ } else if (isExpression(value)) {
+ optional<std::unique_ptr<Expression>> expression = convert<std::unique_ptr<Expression>>(value, error, valueTypeToExpressionType<T>());
+ if (!expression) {
+ return {};
+ }
+ if (isFeatureConstant(**expression)) {
+ return { CameraFunction<T>(std::move(*expression)) };
+ } else {
+ error = { "property expressions not supported" };
+ return {};
+ }
} else if (isObject(value)) {
optional<CameraFunction<T>> function = convert<CameraFunction<T>>(value, error);
if (!function) {
diff --git a/include/mbgl/style/conversion/source.hpp b/include/mbgl/style/conversion/source.hpp
index e0563ac10b..2cf2e36da4 100644
--- a/include/mbgl/style/conversion/source.hpp
+++ b/include/mbgl/style/conversion/source.hpp
@@ -1,16 +1,9 @@
#pragma once
#include <mbgl/style/conversion.hpp>
-#include <mbgl/style/conversion/coordinate.hpp>
-#include <mbgl/style/conversion/geojson.hpp>
-#include <mbgl/style/conversion/geojson_options.hpp>
-#include <mbgl/style/conversion/tileset.hpp>
#include <mbgl/style/source.hpp>
-#include <mbgl/style/sources/geojson_source.hpp>
-#include <mbgl/style/sources/raster_source.hpp>
-#include <mbgl/style/sources/vector_source.hpp>
-#include <mbgl/style/sources/image_source.hpp>
-#include <mbgl/util/geo.hpp>
+
+#include <memory>
namespace mbgl {
namespace style {
@@ -19,170 +12,7 @@ namespace conversion {
template <>
struct Converter<std::unique_ptr<Source>> {
public:
-
- template <class V>
- optional<std::unique_ptr<Source>> operator()(const V& value, Error& error, const std::string& id) const {
- if (!isObject(value)) {
- error = { "source must be an object" };
- return {};
- }
-
- auto typeValue = objectMember(value, "type");
- if (!typeValue) {
- error = { "source must have a type" };
- return {};
- }
-
- optional<std::string> type = toString(*typeValue);
- if (!type) {
- error = { "source type must be a string" };
- return {};
- }
-
- if (*type == "raster") {
- return convertRasterSource(id, value, error);
- } else if (*type == "vector") {
- return convertVectorSource(id, value, error);
- } else if (*type == "geojson") {
- return convertGeoJSONSource(id, value, error);
- } else if (*type == "image") {
- return convertImageSource(id, value, error);
- } else {
- error = { "invalid source type" };
- return {};
- }
- }
-
-private:
- // A tile source can either specify a URL to TileJSON, or inline TileJSON.
- template <class V>
- optional<variant<std::string, Tileset>> convertURLOrTileset(const V& value, Error& error) const {
- auto urlVal = objectMember(value, "url");
- if (!urlVal) {
- optional<Tileset> tileset = convert<Tileset>(value, error);
- if (!tileset) {
- return {};
- }
- return { *tileset };
- }
-
- optional<std::string> url = toString(*urlVal);
- if (!url) {
- error = { "source url must be a string" };
- return {};
- }
-
- return { *url };
- }
-
- template <class V>
- optional<std::unique_ptr<Source>> convertRasterSource(const std::string& id,
- const V& value,
- Error& error) const {
- optional<variant<std::string, Tileset>> urlOrTileset = convertURLOrTileset(value, error);
- if (!urlOrTileset) {
- return {};
- }
-
- uint16_t tileSize = util::tileSize;
- auto tileSizeValue = objectMember(value, "tileSize");
- if (tileSizeValue) {
- optional<float> size = toNumber(*tileSizeValue);
- if (!size || *size < 0 || *size > std::numeric_limits<uint16_t>::max()) {
- error = { "invalid tileSize" };
- return {};
- }
- tileSize = *size;
- }
-
- return { std::make_unique<RasterSource>(id, std::move(*urlOrTileset), tileSize) };
- }
-
- template <class V>
- optional<std::unique_ptr<Source>> convertVectorSource(const std::string& id,
- const V& value,
- Error& error) const {
- optional<variant<std::string, Tileset>> urlOrTileset = convertURLOrTileset(value, error);
- if (!urlOrTileset) {
- return {};
- }
-
- return { std::make_unique<VectorSource>(id, std::move(*urlOrTileset)) };
- }
-
- template <class V>
- optional<std::unique_ptr<Source>> convertGeoJSONSource(const std::string& id,
- const V& value,
- Error& error) const {
- auto dataValue = objectMember(value, "data");
- if (!dataValue) {
- error = { "GeoJSON source must have a data value" };
- return {};
- }
-
- optional<GeoJSONOptions> options = convert<GeoJSONOptions>(value, error);
- if (!options) {
- return {};
- }
-
- auto result = std::make_unique<GeoJSONSource>(id, *options);
-
- if (isObject(*dataValue)) {
- optional<GeoJSON> geoJSON = convert<GeoJSON>(*dataValue, error);
- if (!geoJSON) {
- return {};
- }
- result->setGeoJSON(std::move(*geoJSON));
- } else if (toString(*dataValue)) {
- result->setURL(*toString(*dataValue));
- } else {
- error = { "GeoJSON data must be a URL or an object" };
- return {};
- }
-
- return { std::move(result) };
- }
-
- template <class V>
- optional<std::unique_ptr<Source>> convertImageSource(const std::string& id,
- const V& value,
- Error& error) const {
- auto urlValue = objectMember(value, "url");
- if (!urlValue) {
- error = { "Image source must have a url value" };
- return {};
- }
-
- auto urlString = toString(*urlValue);
- if (!urlString) {
- error = { "Image url must be a URL string" };
- return {};
- }
-
- auto coordinatesValue = objectMember(value, "coordinates");
- if (!coordinatesValue) {
- error = { "Image source must have a coordinates values" };
- return {};
- }
-
- if (!isArray(*coordinatesValue) || arrayLength(*coordinatesValue) != 4) {
- error = { "Image coordinates must be an array of four longitude latitude pairs" };
- return {};
- }
-
- std::array<LatLng, 4> coordinates;
- for (std::size_t i=0; i < 4; i++) {
- auto latLng = conversion::convert<LatLng>(arrayMember(*coordinatesValue,i), error);
- if (!latLng) {
- return {};
- }
- coordinates[i] = *latLng;
- }
- auto result = std::make_unique<ImageSource>(id, coordinates);
- result->setURL(*urlString);
-
- return { std::move(result) };
- }
+ optional<std::unique_ptr<Source>> operator()(const Convertible& value, Error& error, const std::string& id) const;
};
} // namespace conversion
diff --git a/include/mbgl/style/conversion/tileset.hpp b/include/mbgl/style/conversion/tileset.hpp
index 377170aa6a..1fb4acf70d 100644
--- a/include/mbgl/style/conversion/tileset.hpp
+++ b/include/mbgl/style/conversion/tileset.hpp
@@ -10,70 +10,7 @@ namespace conversion {
template <>
struct Converter<Tileset> {
public:
- template <class V>
- optional<Tileset> operator()(const V& value, Error& error) const {
- Tileset result;
-
- auto tiles = objectMember(value, "tiles");
- if (!tiles) {
- error = { "source must have tiles" };
- return {};
- }
-
- if (!isArray(*tiles)) {
- error = { "source tiles must be an array" };
- return {};
- }
-
- for (std::size_t i = 0; i < arrayLength(*tiles); i++) {
- optional<std::string> urlTemplate = toString(arrayMember(*tiles, i));
- if (!urlTemplate) {
- error = { "source tiles member must be a string" };
- return {};
- }
- result.tiles.push_back(std::move(*urlTemplate));
- }
-
- auto schemeValue = objectMember(value, "scheme");
- if (schemeValue) {
- optional<std::string> scheme = toString(*schemeValue);
- if (scheme && *scheme == "tms") {
- result.scheme = Tileset::Scheme::TMS;
- }
- }
-
- auto minzoomValue = objectMember(value, "minzoom");
- if (minzoomValue) {
- optional<float> minzoom = toNumber(*minzoomValue);
- if (!minzoom || *minzoom < 0 || *minzoom > std::numeric_limits<uint8_t>::max()) {
- error = { "invalid minzoom" };
- return {};
- }
- result.zoomRange.min = *minzoom;
- }
-
- auto maxzoomValue = objectMember(value, "maxzoom");
- if (maxzoomValue) {
- optional<float> maxzoom = toNumber(*maxzoomValue);
- if (!maxzoom || *maxzoom < 0 || *maxzoom > std::numeric_limits<uint8_t>::max()) {
- error = { "invalid maxzoom" };
- return {};
- }
- result.zoomRange.max = *maxzoom;
- }
-
- auto attributionValue = objectMember(value, "attribution");
- if (attributionValue) {
- optional<std::string> attribution = toString(*attributionValue);
- if (!attribution) {
- error = { "source attribution must be a string" };
- return {};
- }
- result.attribution = std::move(*attribution);
- }
-
- return result;
- }
+ optional<Tileset> operator()(const Convertible& value, Error& error) const;
};
} // namespace conversion
diff --git a/include/mbgl/style/conversion/transition_options.hpp b/include/mbgl/style/conversion/transition_options.hpp
index de8834d578..0563f39ac3 100644
--- a/include/mbgl/style/conversion/transition_options.hpp
+++ b/include/mbgl/style/conversion/transition_options.hpp
@@ -10,37 +10,7 @@ namespace conversion {
template <>
struct Converter<TransitionOptions> {
public:
- template <class V>
- optional<TransitionOptions> operator()(const V& value, Error& error) const {
- if (!isObject(value)) {
- error = { "transition must be an object" };
- return {};
- }
-
- TransitionOptions result;
-
- auto duration = objectMember(value, "duration");
- if (duration) {
- auto number = toNumber(*duration);
- if (!number) {
- error = { "duration must be a number" };
- return {};
- }
- result.duration = { std::chrono::milliseconds(int64_t(*number)) };
- }
-
- auto delay = objectMember(value, "delay");
- if (delay) {
- auto number = toNumber(*delay);
- if (!number) {
- error = { "delay must be a number" };
- return {};
- }
- result.delay = { std::chrono::milliseconds(int64_t(*number)) };
- }
-
- return result;
- }
+ optional<TransitionOptions> operator()(const Convertible& value, Error& error) const;
};
} // namespace conversion
diff --git a/include/mbgl/style/expression/array_assertion.hpp b/include/mbgl/style/expression/array_assertion.hpp
new file mode 100644
index 0000000000..2516eea024
--- /dev/null
+++ b/include/mbgl/style/expression/array_assertion.hpp
@@ -0,0 +1,39 @@
+#pragma once
+
+#include <mbgl/style/expression/expression.hpp>
+#include <mbgl/style/expression/type.hpp>
+#include <mbgl/style/expression/parsing_context.hpp>
+#include <mbgl/style/conversion.hpp>
+
+#include <memory>
+
+namespace mbgl {
+namespace style {
+namespace expression {
+
+class ArrayAssertion : public Expression {
+public:
+ ArrayAssertion(type::Array type_, std::unique_ptr<Expression> input_) :
+ Expression(type_),
+ input(std::move(input_))
+ {}
+
+ static ParseResult parse(const mbgl::style::conversion::Convertible& value, ParsingContext& ctx);
+
+ EvaluationResult evaluate(const EvaluationContext& params) const override;
+ void eachChild(const std::function<void(const Expression&)>& visit) const override;
+
+ bool operator==(const Expression& e) const override {
+ if (auto rhs = dynamic_cast<const ArrayAssertion*>(&e)) {
+ return getType() == rhs->getType() && *input == *(rhs->input);
+ }
+ return false;
+ }
+
+private:
+ std::unique_ptr<Expression> input;
+};
+
+} // namespace expression
+} // namespace style
+} // namespace mbgl
diff --git a/include/mbgl/style/expression/assertion.hpp b/include/mbgl/style/expression/assertion.hpp
new file mode 100644
index 0000000000..504d49f4e5
--- /dev/null
+++ b/include/mbgl/style/expression/assertion.hpp
@@ -0,0 +1,33 @@
+#pragma once
+#include <mbgl/style/expression/expression.hpp>
+#include <mbgl/style/conversion.hpp>
+#include <mbgl/style/expression/parsing_context.hpp>
+#include <memory>
+#include <vector>
+
+namespace mbgl {
+namespace style {
+namespace expression {
+
+class Assertion : public Expression {
+public:
+ Assertion(type::Type type_, std::vector<std::unique_ptr<Expression>> inputs_) :
+ Expression(type_),
+ inputs(std::move(inputs_))
+ {}
+
+ static ParseResult parse(const mbgl::style::conversion::Convertible& value, ParsingContext& ctx);
+
+ EvaluationResult evaluate(const EvaluationContext& params) const override;
+ void eachChild(const std::function<void(const Expression&)>& visit) const override;
+
+ bool operator==(const Expression& e) const override;
+
+private:
+ std::vector<std::unique_ptr<Expression>> inputs;
+};
+
+} // namespace expression
+} // namespace style
+} // namespace mbgl
+
diff --git a/include/mbgl/style/expression/at.hpp b/include/mbgl/style/expression/at.hpp
new file mode 100644
index 0000000000..e3eefa4fe8
--- /dev/null
+++ b/include/mbgl/style/expression/at.hpp
@@ -0,0 +1,38 @@
+#pragma once
+
+#include <mbgl/style/expression/expression.hpp>
+#include <mbgl/style/conversion.hpp>
+#include <memory>
+
+namespace mbgl {
+namespace style {
+namespace expression {
+
+class At : public Expression {
+public:
+ At(std::unique_ptr<Expression> index_, std::unique_ptr<Expression> input_) :
+ Expression(input_->getType().get<type::Array>().itemType),
+ index(std::move(index_)),
+ input(std::move(input_))
+ {}
+
+ static ParseResult parse(const mbgl::style::conversion::Convertible& value, ParsingContext& ctx);
+
+ EvaluationResult evaluate(const EvaluationContext& params) const override;
+ void eachChild(const std::function<void(const Expression&)>&) const override;
+
+ bool operator==(const Expression& e) const override {
+ if (auto rhs = dynamic_cast<const At*>(&e)) {
+ return *index == *(rhs->index) && *input == *(rhs->input);
+ }
+ return false;
+ }
+
+private:
+ std::unique_ptr<Expression> index;
+ std::unique_ptr<Expression> input;
+};
+
+} // namespace expression
+} // namespace style
+} // namespace mbgl
diff --git a/include/mbgl/style/expression/boolean_operator.hpp b/include/mbgl/style/expression/boolean_operator.hpp
new file mode 100644
index 0000000000..01231d706b
--- /dev/null
+++ b/include/mbgl/style/expression/boolean_operator.hpp
@@ -0,0 +1,49 @@
+#pragma once
+
+#include <mbgl/style/expression/expression.hpp>
+#include <mbgl/style/conversion.hpp>
+#include <memory>
+
+namespace mbgl {
+namespace style {
+namespace expression {
+
+class Any : public Expression {
+public:
+ Any(std::vector<std::unique_ptr<Expression>> inputs_) :
+ Expression(type::Boolean),
+ inputs(std::move(inputs_))
+ {}
+
+ static ParseResult parse(const mbgl::style::conversion::Convertible& value, ParsingContext& ctx);
+
+ EvaluationResult evaluate(const EvaluationContext& params) const override;
+ void eachChild(const std::function<void(const Expression&)>& visit) const override;
+ bool operator==(const Expression& e) const override;
+
+private:
+ std::vector<std::unique_ptr<Expression>> inputs;
+};
+
+class All : public Expression {
+public:
+ All(std::vector<std::unique_ptr<Expression>> inputs_) :
+ Expression(type::Boolean),
+ inputs(std::move(inputs_))
+ {}
+
+ static ParseResult parse(const mbgl::style::conversion::Convertible& value, ParsingContext& ctx);
+
+ EvaluationResult evaluate(const EvaluationContext& params) const override;
+ void eachChild(const std::function<void(const Expression&)>& visit) const override;
+
+ bool operator==(const Expression& e) const override;
+
+private:
+ std::vector<std::unique_ptr<Expression>> inputs;
+};
+
+} // namespace expression
+} // namespace style
+} // namespace mbgl
+
diff --git a/include/mbgl/style/expression/case.hpp b/include/mbgl/style/expression/case.hpp
new file mode 100644
index 0000000000..ece2fe0329
--- /dev/null
+++ b/include/mbgl/style/expression/case.hpp
@@ -0,0 +1,36 @@
+#pragma once
+
+#include <mbgl/style/expression/expression.hpp>
+#include <mbgl/style/conversion.hpp>
+#include <mbgl/style/expression/parsing_context.hpp>
+
+#include <memory>
+#include <vector>
+
+namespace mbgl {
+namespace style {
+namespace expression {
+
+class Case : public Expression {
+public:
+ using Branch = std::pair<std::unique_ptr<Expression>, std::unique_ptr<Expression>>;
+
+ Case(type::Type type_, std::vector<Branch> branches_, std::unique_ptr<Expression> otherwise_)
+ : Expression(type_), branches(std::move(branches_)), otherwise(std::move(otherwise_)) {
+ }
+
+ static ParseResult parse(const mbgl::style::conversion::Convertible& value, ParsingContext& ctx);
+
+ EvaluationResult evaluate(const EvaluationContext& params) const override;
+ void eachChild(const std::function<void(const Expression&)>& visit) const override;
+
+ bool operator==(const Expression& e) const override;
+
+private:
+ std::vector<Branch> branches;
+ std::unique_ptr<Expression> otherwise;
+};
+
+} // namespace expression
+} // namespace style
+} // namespace mbgl
diff --git a/include/mbgl/style/expression/check_subtype.hpp b/include/mbgl/style/expression/check_subtype.hpp
new file mode 100644
index 0000000000..90e5169de7
--- /dev/null
+++ b/include/mbgl/style/expression/check_subtype.hpp
@@ -0,0 +1,17 @@
+#pragma once
+
+#include <mbgl/style/expression/type.hpp>
+#include <mbgl/util/optional.hpp>
+#include <memory>
+
+namespace mbgl {
+namespace style {
+namespace expression {
+namespace type {
+
+optional<std::string> checkSubtype(const Type& expected, const Type& t);
+
+} // namespace type
+} // namespace expression
+} // namespace style
+} // namespace mbgl
diff --git a/include/mbgl/style/expression/coalesce.hpp b/include/mbgl/style/expression/coalesce.hpp
new file mode 100644
index 0000000000..4e6a9b3793
--- /dev/null
+++ b/include/mbgl/style/expression/coalesce.hpp
@@ -0,0 +1,45 @@
+#pragma once
+
+#include <mbgl/style/expression/expression.hpp>
+#include <mbgl/style/conversion.hpp>
+#include <mbgl/style/expression/parsing_context.hpp>
+
+#include <memory>
+#include <map>
+
+namespace mbgl {
+namespace style {
+namespace expression {
+
+class Coalesce : public Expression {
+public:
+ using Args = std::vector<std::unique_ptr<Expression>>;
+ Coalesce(const type::Type& type_, Args args_) :
+ Expression(type_),
+ args(std::move(args_))
+ {}
+
+ static ParseResult parse(const mbgl::style::conversion::Convertible& value, ParsingContext& ctx);
+
+
+ EvaluationResult evaluate(const EvaluationContext& params) const override;
+
+ void eachChild(const std::function<void(const Expression&)>& visit) const override;
+
+ bool operator==(const Expression& e) const override;
+
+ std::size_t getLength() const {
+ return args.size();
+ }
+
+ Expression* getChild(std::size_t i) const {
+ return args.at(i).get();
+ }
+
+private:
+ Args args;
+};
+
+} // namespace expression
+} // namespace style
+} // namespace mbgl
diff --git a/include/mbgl/style/expression/coercion.hpp b/include/mbgl/style/expression/coercion.hpp
new file mode 100644
index 0000000000..665bb7ce7c
--- /dev/null
+++ b/include/mbgl/style/expression/coercion.hpp
@@ -0,0 +1,34 @@
+#pragma once
+#include <mbgl/style/expression/expression.hpp>
+#include <mbgl/style/conversion.hpp>
+#include <memory>
+#include <vector>
+
+namespace mbgl {
+namespace style {
+namespace expression {
+
+/**
+ * Special form for error-coalescing coercion expressions "to-number",
+ * "to-color". Since these coercions can fail at runtime, they accept multiple
+ * arguments, only evaluating one at a time until one succeeds.
+ */
+class Coercion : public Expression {
+public:
+ Coercion(type::Type type_, std::vector<std::unique_ptr<Expression>> inputs_);
+
+ static ParseResult parse(const mbgl::style::conversion::Convertible& value, ParsingContext& ctx);
+
+ EvaluationResult evaluate(const EvaluationContext& params) const override;
+ void eachChild(const std::function<void(const Expression&)>& visit) const override;
+
+ bool operator==(const Expression& e) const override;
+private:
+ EvaluationResult (*coerceSingleValue) (const Value& v);
+ std::vector<std::unique_ptr<Expression>> inputs;
+};
+
+} // namespace expression
+} // namespace style
+} // namespace mbgl
+
diff --git a/include/mbgl/style/expression/compound_expression.hpp b/include/mbgl/style/expression/compound_expression.hpp
new file mode 100644
index 0000000000..fc3edbfd4a
--- /dev/null
+++ b/include/mbgl/style/expression/compound_expression.hpp
@@ -0,0 +1,138 @@
+#pragma once
+
+#include <mbgl/style/expression/expression.hpp>
+#include <mbgl/style/conversion.hpp>
+#include <mbgl/style/expression/parsing_context.hpp>
+#include <mbgl/style/expression/type.hpp>
+#include <mbgl/style/expression/value.hpp>
+
+#include <mbgl/util/optional.hpp>
+#include <mbgl/util/variant.hpp>
+
+#include <memory>
+#include <vector>
+
+namespace mbgl {
+namespace style {
+namespace expression {
+
+/*
+ CompoundExpression provides a mechanism for implementing an expression
+ simply by providing a list of pure functions of the form
+ (const T0& arg0, const T1& arg1, ...) -> Result<U> where T0, T1, ..., U are
+ member types of mbgl::style::expression::Value.
+
+ The majority of expressions specified in the style-spec are implemented in
+ this fashion (see compound_expression.cpp).
+*/
+
+
+/*
+ Represents the parameter list for an expression that takes an arbitrary
+ number of arguments (of a specific type).
+*/
+struct VarargsType { type::Type type; };
+template <typename T>
+struct Varargs : std::vector<T> { using std::vector<T>::vector; };
+
+namespace detail {
+// Base class for the Signature<Fn> structs that are used to determine the
+// 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_) :
+ result(std::move(result_)),
+ params(std::move(params_))
+ {}
+ virtual ~SignatureBase() = default;
+ virtual std::unique_ptr<Expression> makeExpression(const std::string& name, std::vector<std::unique_ptr<Expression>>) const = 0;
+ type::Type result;
+ variant<std::vector<type::Type>, VarargsType> params;
+};
+} // namespace detail
+
+
+/*
+ Common base class for CompoundExpression<Signature> instances. Used to
+ allow downcasting (and access to things like name & parameter list) during
+ an Expression tree traversal.
+*/
+class CompoundExpressionBase : public Expression {
+public:
+ CompoundExpressionBase(std::string name_, const detail::SignatureBase& signature) :
+ Expression(signature.result),
+ name(std::move(name_)),
+ params(signature.params)
+ {}
+
+ std::string getName() const { return name; }
+ optional<std::size_t> getParameterCount() const {
+ return params.match(
+ [&](const VarargsType&) { return optional<std::size_t>(); },
+ [&](const std::vector<type::Type>& p) -> optional<std::size_t> { return p.size(); }
+ );
+ }
+
+private:
+ std::string name;
+ variant<std::vector<type::Type>, VarargsType> params;
+};
+
+template <typename Signature>
+class CompoundExpression : public CompoundExpressionBase {
+public:
+ using Args = typename Signature::Args;
+
+ CompoundExpression(const std::string& name_,
+ Signature signature_,
+ typename Signature::Args args_) :
+ CompoundExpressionBase(name_, signature_),
+ signature(signature_),
+ args(std::move(args_))
+ {}
+
+ EvaluationResult evaluate(const EvaluationContext& evaluationParams) const override {
+ return signature.apply(evaluationParams, args);
+ }
+
+ void eachChild(const std::function<void(const Expression&)>& visit) const override {
+ for (const std::unique_ptr<Expression>& e : args) {
+ visit(*e);
+ }
+ }
+
+ bool operator==(const Expression& e) const override {
+ if (auto rhs = dynamic_cast<const CompoundExpression*>(&e)) {
+ return getName() == rhs->getName() && Expression::childrenEqual(args, rhs->args);
+ }
+ return false;
+ }
+
+private:
+ Signature signature;
+ typename Signature::Args args;
+};
+
+/*
+ Holds the map of expression name => implementation (which is just one or
+ more evaluation functions, each wrapped in a Signature struct).
+*/
+struct CompoundExpressionRegistry {
+ using Definition = std::vector<std::unique_ptr<detail::SignatureBase>>;
+ static std::unordered_map<std::string, Definition> definitions;
+};
+
+ParseResult parseCompoundExpression(const std::string name, const mbgl::style::conversion::Convertible& value, ParsingContext& ctx);
+
+ParseResult createCompoundExpression(const std::string& name,
+ const CompoundExpressionRegistry::Definition& definition,
+ std::vector<std::unique_ptr<Expression>> args,
+ ParsingContext& ctx);
+
+ParseResult createCompoundExpression(const std::string& name,
+ std::vector<std::unique_ptr<Expression>> args,
+ ParsingContext& ctx);
+
+} // namespace expression
+} // namespace style
+} // namespace mbgl
diff --git a/include/mbgl/style/expression/expression.hpp b/include/mbgl/style/expression/expression.hpp
new file mode 100644
index 0000000000..1954d8b090
--- /dev/null
+++ b/include/mbgl/style/expression/expression.hpp
@@ -0,0 +1,169 @@
+#pragma once
+
+#include <array>
+#include <vector>
+#include <memory>
+#include <mbgl/util/optional.hpp>
+#include <mbgl/util/variant.hpp>
+#include <mbgl/util/color.hpp>
+#include <mbgl/style/expression/type.hpp>
+#include <mbgl/style/expression/value.hpp>
+#include <mbgl/style/expression/parsing_context.hpp>
+
+namespace mbgl {
+
+class GeometryTileFeature;
+
+namespace style {
+namespace expression {
+
+class EvaluationError {
+public:
+ std::string message;
+};
+
+class EvaluationContext {
+public:
+ EvaluationContext(float zoom_) : zoom(zoom_), feature(nullptr) {}
+ EvaluationContext(GeometryTileFeature const * feature_) : zoom(optional<float>()), feature(feature_) {}
+ EvaluationContext(float zoom_, GeometryTileFeature const * feature_) :
+ zoom(zoom_), feature(feature_)
+ {}
+ EvaluationContext(optional<float> zoom_, GeometryTileFeature const * feature_, optional<double> heatmapDensity_) :
+ zoom(std::move(zoom_)), feature(feature_), heatmapDensity(std::move(heatmapDensity_))
+ {}
+
+ optional<float> zoom;
+ GeometryTileFeature const * feature;
+ optional<double> heatmapDensity;
+};
+
+template<typename T>
+class Result : private variant<EvaluationError, T> {
+public:
+ using variant<EvaluationError, T>::variant;
+ using Value = T;
+
+ explicit operator bool () const {
+ return this->template is<T>();
+ }
+
+ // optional does some type trait magic for this one, so this might
+ // be problematic as is.
+ const T* operator->() const {
+ assert(this->template is<T>());
+ return std::addressof(this->template get<T>());
+ }
+
+ T* operator->() {
+ assert(this->template is<T>());
+ return std::addressof(this->template get<T>());
+ }
+
+ T& operator*() {
+ assert(this->template is<T>());
+ return this->template get<T>();
+ }
+
+ const T& operator*() const {
+ assert(this->template is<T>());
+ return this->template get<T>();
+ }
+
+ const EvaluationError& error() const {
+ assert(this->template is<EvaluationError>());
+ return this->template get<EvaluationError>();
+ }
+};
+
+class EvaluationResult : public Result<Value> {
+public:
+ using Result::Result; // NOLINT
+
+ EvaluationResult(const std::array<double, 4>& arr) :
+ Result(toExpressionValue(arr))
+ {}
+
+ // used only for the special (private) "error" expression
+ EvaluationResult(const type::ErrorType&) {
+ assert(false);
+ }
+};
+
+/*
+ Expression is an abstract class that serves as an interface and base class
+ for particular expression implementations.
+
+ CompoundExpression implements the majority of expressions in the spec by
+ inferring the argument and output from a simple function (const T0& arg0,
+ const T1& arg1, ...) -> Result<U> where T0, T1, ..., U are member types of
+ mbgl::style::expression::Value.
+
+ The other Expression subclasses (Let, Curve, Match, etc.) exist in order to
+ implement expressions that need specialized parsing, type checking, or
+ evaluation logic that can't be handled by CompoundExpression's inference
+ mechanism.
+
+ Each Expression subclass also provides a static
+ ParseResult ExpressionClass::parse(const V&, ParsingContext),
+ which handles parsing a style-spec JSON representation of the expression.
+*/
+class Expression {
+public:
+ Expression(type::Type type_) : type(std::move(type_)) {}
+ virtual ~Expression() = default;
+
+ virtual EvaluationResult evaluate(const EvaluationContext& params) const = 0;
+ virtual void eachChild(const std::function<void(const Expression&)>&) const = 0;
+ virtual bool operator==(const Expression&) const = 0;
+ bool operator!=(const Expression& rhs) const {
+ return !operator==(rhs);
+ }
+
+ type::Type getType() const { return type; };
+
+ EvaluationResult evaluate(optional<float> zoom, const Feature& feature, optional<double> heatmapDensity) const;
+
+protected:
+ template <typename T>
+ static bool childrenEqual(const T& lhs, const T& rhs) {
+ if (lhs.size() != rhs.size()) return false;
+ for (auto leftChild = lhs.begin(), rightChild = rhs.begin();
+ leftChild != lhs.end();
+ leftChild++, rightChild++)
+ {
+ if (!Expression::childEqual(*leftChild, *rightChild)) return false;
+ }
+ return true;
+ }
+
+ static bool childEqual(const std::unique_ptr<Expression>& lhs, const std::unique_ptr<Expression>& rhs) {
+ return *lhs == *rhs;
+ }
+
+ template <typename T>
+ static bool childEqual(const std::pair<T, std::unique_ptr<Expression>>& lhs,
+ const std::pair<T, std::unique_ptr<Expression>>& rhs) {
+ return lhs.first == rhs.first && *(lhs.second) == *(rhs.second);
+ }
+
+ template <typename T>
+ static bool childEqual(const std::pair<T, std::shared_ptr<Expression>>& lhs,
+ const std::pair<T, std::shared_ptr<Expression>>& rhs) {
+ return lhs.first == rhs.first && *(lhs.second) == *(rhs.second);
+ }
+
+ static bool childEqual(const std::pair<std::unique_ptr<Expression>, std::unique_ptr<Expression>>& lhs,
+ const std::pair<std::unique_ptr<Expression>, std::unique_ptr<Expression>>& rhs) {
+ return *(lhs.first) == *(rhs.first) && *(lhs.second) == *(rhs.second);
+ }
+
+
+
+private:
+ type::Type type;
+};
+
+} // namespace expression
+} // namespace style
+} // namespace mbgl
diff --git a/include/mbgl/style/expression/find_zoom_curve.hpp b/include/mbgl/style/expression/find_zoom_curve.hpp
new file mode 100644
index 0000000000..6301938033
--- /dev/null
+++ b/include/mbgl/style/expression/find_zoom_curve.hpp
@@ -0,0 +1,20 @@
+#pragma once
+
+#include <mbgl/style/expression/parsing_context.hpp>
+#include <mbgl/style/expression/interpolate.hpp>
+#include <mbgl/style/expression/step.hpp>
+
+#include <mbgl/util/variant.hpp>
+#include <mbgl/util/optional.hpp>
+
+namespace mbgl {
+namespace style {
+namespace expression {
+
+optional<variant<const InterpolateBase*, const Step*, ParsingError>> findZoomCurve(const expression::Expression* e);
+
+variant<const InterpolateBase*, const Step*> findZoomCurveChecked(const expression::Expression* e);
+
+} // namespace expression
+} // namespace style
+} // namespace mbgl
diff --git a/include/mbgl/style/expression/get_covering_stops.hpp b/include/mbgl/style/expression/get_covering_stops.hpp
new file mode 100644
index 0000000000..157aefe7bc
--- /dev/null
+++ b/include/mbgl/style/expression/get_covering_stops.hpp
@@ -0,0 +1,18 @@
+#pragma once
+
+#include <mbgl/style/expression/expression.hpp>
+#include <mbgl/util/range.hpp>
+#include <memory>
+#include <map>
+
+namespace mbgl {
+namespace style {
+namespace expression {
+
+// Return the smallest range of stops that covers the interval [lower, upper]
+Range<float> getCoveringStops(const std::map<double, std::unique_ptr<Expression>>& stops,
+ const double lower, const double upper);
+
+} // namespace expression
+} // namespace style
+} // namespace mbgl
diff --git a/include/mbgl/style/expression/interpolate.hpp b/include/mbgl/style/expression/interpolate.hpp
new file mode 100644
index 0000000000..2dcb5a32a4
--- /dev/null
+++ b/include/mbgl/style/expression/interpolate.hpp
@@ -0,0 +1,177 @@
+#pragma once
+
+#include <mbgl/style/expression/expression.hpp>
+#include <mbgl/style/expression/parsing_context.hpp>
+#include <mbgl/style/expression/get_covering_stops.hpp>
+#include <mbgl/style/conversion.hpp>
+
+#include <mbgl/util/interpolate.hpp>
+#include <mbgl/util/range.hpp>
+#include <mbgl/util/unitbezier.hpp>
+
+#include <memory>
+#include <map>
+
+
+namespace mbgl {
+namespace style {
+namespace expression {
+
+class ExponentialInterpolator {
+public:
+ ExponentialInterpolator(double base_) : base(base_) {}
+
+ double base;
+
+ double interpolationFactor(const Range<double>& inputLevels, const double input) const {
+ return util::interpolationFactor(base,
+ Range<float> {
+ static_cast<float>(inputLevels.min),
+ static_cast<float>(inputLevels.max)
+ },
+ input);
+ }
+
+ bool operator==(const ExponentialInterpolator& rhs) const {
+ return base == rhs.base;
+ }
+};
+
+class CubicBezierInterpolator {
+public:
+ CubicBezierInterpolator(double x1_, double y1_, double x2_, double y2_) : ub(x1_, y1_, x2_, y2_) {}
+
+ double interpolationFactor(const Range<double>& inputLevels, const double input) const {
+ return ub.solve(input / (inputLevels.max - inputLevels.min), 1e-6);
+ }
+
+ bool operator==(const CubicBezierInterpolator& rhs) const {
+ return ub == rhs.ub;
+ }
+
+ util::UnitBezier ub;
+};
+
+
+ParseResult parseInterpolate(const mbgl::style::conversion::Convertible& value, ParsingContext& ctx);
+
+class InterpolateBase : public Expression {
+public:
+ using Interpolator = variant<ExponentialInterpolator, CubicBezierInterpolator>;
+
+ InterpolateBase(const type::Type& type_,
+ Interpolator interpolator_,
+ std::unique_ptr<Expression> input_,
+ std::map<double, std::unique_ptr<Expression>> stops_
+ ) : Expression(type_),
+ interpolator(std::move(interpolator_)),
+ input(std::move(input_)),
+ stops(std::move(stops_))
+ {}
+
+ const std::unique_ptr<Expression>& getInput() const { return input; }
+
+ void eachChild(const std::function<void(const Expression&)>& visit) const override {
+ visit(*input);
+ for (const std::pair<const double, const std::unique_ptr<Expression>&>& stop : stops) {
+ visit(*stop.second);
+ }
+ }
+
+ // Return the smallest range of stops that covers the interval [lower, upper]
+ Range<float> getCoveringStops(const double lower, const double upper) const {
+ return ::mbgl::style::expression::getCoveringStops(stops, lower, upper);
+ }
+
+ double interpolationFactor(const Range<double>& inputLevels, const double inputValue) const {
+ return interpolator.match(
+ [&](const auto& interp) { return interp.interpolationFactor(inputLevels, inputValue); }
+ );
+ }
+
+protected:
+ const Interpolator interpolator;
+ const std::unique_ptr<Expression> input;
+ const std::map<double, std::unique_ptr<Expression>> stops;
+};
+
+template <typename T>
+class Interpolate : public InterpolateBase {
+public:
+ Interpolate(type::Type type_,
+ Interpolator interpolator_,
+ std::unique_ptr<Expression> input_,
+ std::map<double, std::unique_ptr<Expression>> stops_
+ ) : InterpolateBase(std::move(type_), std::move(interpolator_), std::move(input_), std::move(stops_))
+ {
+ static_assert(util::Interpolatable<T>::value, "Interpolate expression requires an interpolatable value type.");
+ }
+
+ EvaluationResult evaluate(const EvaluationContext& params) const override {
+ const EvaluationResult evaluatedInput = input->evaluate(params);
+ if (!evaluatedInput) { return evaluatedInput.error(); }
+ float x = *fromExpressionValue<float>(*evaluatedInput);
+
+ if (stops.empty()) {
+ return EvaluationError { "No stops in exponential curve." };
+ }
+
+ auto it = stops.upper_bound(x);
+ if (it == stops.end()) {
+ return stops.rbegin()->second->evaluate(params);
+ } else if (it == stops.begin()) {
+ return stops.begin()->second->evaluate(params);
+ } else {
+ float t = interpolationFactor({ std::prev(it)->first, it->first }, x);
+
+ if (t == 0.0f) {
+ return std::prev(it)->second->evaluate(params);
+ }
+ if (t == 1.0f) {
+ return it->second->evaluate(params);
+ }
+
+ EvaluationResult lower = std::prev(it)->second->evaluate(params);
+ if (!lower) {
+ return lower.error();
+ }
+ EvaluationResult upper = it->second->evaluate(params);
+ if (!upper) {
+ return upper.error();
+ }
+
+ if (!lower->is<T>()) {
+ return EvaluationError {
+ "Expected value to be of type " + toString(valueTypeToExpressionType<T>()) +
+ ", but found " + toString(typeOf(*lower)) + " instead."
+ };
+ }
+
+ if (!upper->is<T>()) {
+ return EvaluationError {
+ "Expected value to be of type " + toString(valueTypeToExpressionType<T>()) +
+ ", but found " + toString(typeOf(*upper)) + " instead."
+ };
+ }
+ return util::interpolate(lower->get<T>(), upper->get<T>(), t);
+ }
+ }
+
+ bool operator==(const Expression& e) const override {
+ if (auto rhs = dynamic_cast<const Interpolate*>(&e)) {
+ if (interpolator != rhs->interpolator ||
+ *input != *(rhs->input) ||
+ stops.size() != rhs->stops.size())
+ {
+ return false;
+ }
+
+ return Expression::childrenEqual(stops, rhs->stops);
+ }
+ return false;
+ }
+};
+
+} // namespace expression
+} // namespace style
+} // namespace mbgl
diff --git a/include/mbgl/style/expression/is_constant.hpp b/include/mbgl/style/expression/is_constant.hpp
new file mode 100644
index 0000000000..29e03ccbc0
--- /dev/null
+++ b/include/mbgl/style/expression/is_constant.hpp
@@ -0,0 +1,35 @@
+#pragma once
+
+#include <mbgl/style/expression/expression.hpp>
+#include <mbgl/style/expression/compound_expression.hpp>
+
+namespace mbgl {
+namespace style {
+namespace expression {
+
+template <typename T>
+bool isGlobalPropertyConstant(const Expression& expression, const T& properties) {
+ if (auto e = dynamic_cast<const CompoundExpressionBase*>(&expression)) {
+ for (const std::string& property : properties) {
+ if (e->getName() == property) {
+ return false;
+ }
+ }
+ }
+
+ bool isConstant = true;
+ expression.eachChild([&](const Expression& e) {
+ if (isConstant && !isGlobalPropertyConstant(e, properties)) {
+ isConstant = false;
+ }
+ });
+ return isConstant;
+};
+
+bool isFeatureConstant(const Expression& expression);
+bool isZoomConstant(const Expression& e);
+
+
+} // namespace expression
+} // namespace style
+} // namespace mbgl
diff --git a/include/mbgl/style/expression/is_expression.hpp b/include/mbgl/style/expression/is_expression.hpp
new file mode 100644
index 0000000000..77c489619c
--- /dev/null
+++ b/include/mbgl/style/expression/is_expression.hpp
@@ -0,0 +1,13 @@
+#pragma once
+
+#include <mbgl/style/expression/expression.hpp>
+
+namespace mbgl {
+namespace style {
+namespace expression {
+
+bool isExpression(const conversion::Convertible& value);
+
+} // namespace expression
+} // namespace style
+} // namespace mbgl
diff --git a/include/mbgl/style/expression/let.hpp b/include/mbgl/style/expression/let.hpp
new file mode 100644
index 0000000000..aaa16ca0c2
--- /dev/null
+++ b/include/mbgl/style/expression/let.hpp
@@ -0,0 +1,72 @@
+#pragma once
+
+#include <mbgl/style/expression/expression.hpp>
+#include <mbgl/style/expression/parsing_context.hpp>
+#include <mbgl/style/conversion.hpp>
+
+#include <memory>
+#include <map>
+
+namespace mbgl {
+namespace style {
+namespace expression {
+
+class Let : public Expression {
+public:
+ using Bindings = std::map<std::string, std::shared_ptr<Expression>>;
+
+ Let(Bindings bindings_, std::unique_ptr<Expression> result_) :
+ Expression(result_->getType()),
+ bindings(std::move(bindings_)),
+ result(std::move(result_))
+ {}
+
+ static ParseResult parse(const mbgl::style::conversion::Convertible&, ParsingContext&);
+
+ EvaluationResult evaluate(const EvaluationContext& params) const override;
+ void eachChild(const std::function<void(const Expression&)>&) const override;
+
+ bool operator==(const Expression& e) const override {
+ if (auto rhs = dynamic_cast<const Let*>(&e)) {
+ return *result == *(rhs->result);
+ }
+ return false;
+ }
+
+ Expression* getResult() const {
+ return result.get();
+ }
+
+private:
+ Bindings bindings;
+ std::unique_ptr<Expression> result;
+};
+
+class Var : public Expression {
+public:
+ Var(std::string name_, std::shared_ptr<Expression> value_) :
+ Expression(value_->getType()),
+ name(std::move(name_)),
+ value(value_)
+ {}
+
+ static ParseResult parse(const mbgl::style::conversion::Convertible&, ParsingContext&);
+
+ EvaluationResult evaluate(const EvaluationContext& params) const override;
+ void eachChild(const std::function<void(const Expression&)>&) const override;
+
+ bool operator==(const Expression& e) const override {
+ if (auto rhs = dynamic_cast<const Var*>(&e)) {
+ return *value == *(rhs->value);
+ }
+ return false;
+ }
+
+private:
+ std::string name;
+ std::shared_ptr<Expression> value;
+};
+
+} // namespace expression
+} // namespace style
+} // namespace mbgl
diff --git a/include/mbgl/style/expression/literal.hpp b/include/mbgl/style/expression/literal.hpp
new file mode 100644
index 0000000000..a0819c7e73
--- /dev/null
+++ b/include/mbgl/style/expression/literal.hpp
@@ -0,0 +1,38 @@
+#pragma once
+
+#include <mbgl/style/expression/expression.hpp>
+#include <mbgl/style/expression/parsing_context.hpp>
+#include <mbgl/style/conversion.hpp>
+
+#include <memory>
+
+namespace mbgl {
+namespace style {
+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_) {}
+ EvaluationResult evaluate(const EvaluationContext&) const override {
+ return value;
+ }
+
+ static ParseResult parse(const mbgl::style::conversion::Convertible&, ParsingContext&);
+
+ void eachChild(const std::function<void(const Expression&)>&) const override {}
+
+ bool operator==(const Expression& e) const override {
+ if (auto rhs = dynamic_cast<const Literal*>(&e)) {
+ return value == rhs->value;
+ }
+ return false;
+ }
+
+private:
+ Value value;
+};
+
+} // namespace expression
+} // namespace style
+} // namespace mbgl
diff --git a/include/mbgl/style/expression/match.hpp b/include/mbgl/style/expression/match.hpp
new file mode 100644
index 0000000000..e17fe96bfe
--- /dev/null
+++ b/include/mbgl/style/expression/match.hpp
@@ -0,0 +1,45 @@
+#pragma once
+
+#include <mbgl/style/expression/expression.hpp>
+#include <mbgl/style/expression/parsing_context.hpp>
+#include <mbgl/style/conversion.hpp>
+
+#include <memory>
+
+namespace mbgl {
+namespace style {
+namespace expression {
+
+template <typename T>
+class Match : public Expression {
+public:
+ using Branches = std::unordered_map<T, std::shared_ptr<Expression>>;
+
+ Match(type::Type type_,
+ std::unique_ptr<Expression> input_,
+ Branches branches_,
+ std::unique_ptr<Expression> otherwise_
+ ) : Expression(type_),
+ input(std::move(input_)),
+ branches(std::move(branches_)),
+ otherwise(std::move(otherwise_))
+ {}
+
+ void eachChild(const std::function<void(const Expression&)>& visit) const override;
+
+ bool operator==(const Expression& e) const override;
+
+ EvaluationResult evaluate(const EvaluationContext& params) const override;
+
+private:
+
+ std::unique_ptr<Expression> input;
+ Branches branches;
+ std::unique_ptr<Expression> otherwise;
+};
+
+ParseResult parseMatch(const mbgl::style::conversion::Convertible& value, ParsingContext& ctx);
+
+} // namespace expression
+} // namespace style
+} // namespace mbgl
diff --git a/include/mbgl/style/expression/parsing_context.hpp b/include/mbgl/style/expression/parsing_context.hpp
new file mode 100644
index 0000000000..65c5ebe188
--- /dev/null
+++ b/include/mbgl/style/expression/parsing_context.hpp
@@ -0,0 +1,147 @@
+#pragma once
+
+#include <mbgl/util/optional.hpp>
+#include <mbgl/style/expression/type.hpp>
+#include <mbgl/style/conversion.hpp>
+
+#include <map>
+#include <string>
+#include <vector>
+#include <memory>
+
+namespace mbgl {
+namespace style {
+namespace expression {
+
+class Expression;
+
+struct ParsingError {
+ std::string message;
+ std::string key;
+ bool operator==(const ParsingError& rhs) const { return message == rhs.message && key == rhs.key; }
+};
+
+using ParseResult = optional<std::unique_ptr<Expression>>;
+
+namespace detail {
+
+class Scope {
+public:
+ Scope(const std::map<std::string, std::shared_ptr<Expression>>& bindings_, std::shared_ptr<Scope> parent_ = nullptr) :
+ bindings(bindings_),
+ parent(std::move(parent_))
+ {}
+
+ const std::map<std::string, std::shared_ptr<Expression>>& bindings;
+ std::shared_ptr<Scope> parent;
+
+ optional<std::shared_ptr<Expression>> get(const std::string& name) {
+ auto it = bindings.find(name);
+ if (it != bindings.end()) {
+ return {it->second};
+ } else if (parent) {
+ return parent->get(name);
+ } else {
+ return optional<std::shared_ptr<Expression>>();
+ }
+ }
+};
+
+} // namespace detail
+
+class ParsingContext {
+public:
+ ParsingContext() : errors(std::make_shared<std::vector<ParsingError>>()) {}
+ ParsingContext(std::string key_) : key(std::move(key_)), errors(std::make_shared<std::vector<ParsingError>>()) {}
+ explicit ParsingContext(optional<type::Type> expected_)
+ : expected(std::move(expected_)),
+ errors(std::make_shared<std::vector<ParsingError>>())
+ {}
+ ParsingContext(ParsingContext&&) = default;
+
+ ParsingContext(const ParsingContext&) = delete;
+ ParsingContext& operator=(const ParsingContext&) = delete;
+
+ std::string getKey() const { return key; }
+ optional<type::Type> getExpected() const { return expected; }
+ const std::vector<ParsingError>& getErrors() const { return *errors; }
+
+ /*
+ Parse the given style-spec JSON value into an Expression object.
+ Specifically, this function is responsible for determining the expression
+ type (either Literal, or the one named in value[0]) and dispatching to the
+ appropriate ParseXxxx::parse(const V&, ParsingContext) method.
+ */
+ ParseResult parse(const mbgl::style::conversion::Convertible& value);
+
+ /*
+ Parse a child expression.
+ */
+ ParseResult parse(const mbgl::style::conversion::Convertible&,
+ std::size_t,
+ optional<type::Type> = {});
+
+ /*
+ Parse a child expression.
+ */
+ ParseResult parse(const mbgl::style::conversion::Convertible&,
+ std::size_t index,
+ optional<type::Type>,
+ const std::map<std::string, std::shared_ptr<Expression>>&);
+
+ /*
+ Check whether `t` is a subtype of `expected`, collecting an error if not.
+ */
+ optional<std::string> checkType(const type::Type& t);
+
+ optional<std::shared_ptr<Expression>> getBinding(const std::string name) {
+ if (!scope) return optional<std::shared_ptr<Expression>>();
+ return scope->get(name);
+ }
+
+ void error(std::string message) {
+ errors->push_back({message, key});
+ }
+
+ void error(std::string message, std::size_t child) {
+ errors->push_back({message, key + "[" + std::to_string(child) + "]"});
+ }
+
+ void error(std::string message, std::size_t child, std::size_t grandchild) {
+ errors->push_back({message, key + "[" + std::to_string(child) + "][" + std::to_string(grandchild) + "]"});
+ }
+
+ void appendErrors(ParsingContext&& ctx) {
+ errors->reserve(errors->size() + ctx.errors->size());
+ std::move(ctx.errors->begin(), ctx.errors->end(), std::inserter(*errors, errors->end()));
+ ctx.errors->clear();
+ }
+
+ void clearErrors() {
+ errors->clear();
+ }
+
+private:
+ ParsingContext(std::string key_,
+ std::shared_ptr<std::vector<ParsingError>> errors_,
+ optional<type::Type> expected_,
+ std::shared_ptr<detail::Scope> scope_)
+ : key(std::move(key_)),
+ expected(std::move(expected_)),
+ scope(std::move(scope_)),
+ errors(std::move(errors_))
+ {}
+
+ std::string key;
+ optional<type::Type> expected;
+ std::shared_ptr<detail::Scope> scope;
+ std::shared_ptr<std::vector<ParsingError>> errors;
+};
+
+using ParseFunction = ParseResult (*)(const conversion::Convertible&, ParsingContext&);
+using ExpressionRegistry = std::unordered_map<std::string, ParseFunction>;
+const ExpressionRegistry& getExpressionRegistry();
+
+} // namespace expression
+} // namespace style
+} // namespace mbgl
diff --git a/include/mbgl/style/expression/step.hpp b/include/mbgl/style/expression/step.hpp
new file mode 100644
index 0000000000..e3c49bc609
--- /dev/null
+++ b/include/mbgl/style/expression/step.hpp
@@ -0,0 +1,45 @@
+
+#pragma once
+
+#include <mbgl/style/expression/expression.hpp>
+#include <mbgl/style/expression/parsing_context.hpp>
+#include <mbgl/style/conversion.hpp>
+
+#include <mbgl/util/range.hpp>
+
+#include <memory>
+#include <map>
+
+
+namespace mbgl {
+namespace style {
+namespace expression {
+
+class Step : public Expression {
+public:
+ Step(const type::Type& type_,
+ std::unique_ptr<Expression> input_,
+ std::map<double, std::unique_ptr<Expression>> stops_
+ ) : Expression(type_),
+ input(std::move(input_)),
+ stops(std::move(stops_))
+ {}
+
+ EvaluationResult evaluate(const EvaluationContext& params) const override;
+ void eachChild(const std::function<void(const Expression&)>& visit) const override;
+
+ const std::unique_ptr<Expression>& getInput() const { return input; }
+ Range<float> getCoveringStops(const double lower, const double upper) const;
+
+ bool operator==(const Expression& e) const override;
+
+ static ParseResult parse(const mbgl::style::conversion::Convertible& value, ParsingContext& ctx);
+
+private:
+ const std::unique_ptr<Expression> input;
+ const std::map<double, std::unique_ptr<Expression>> stops;
+};
+
+} // namespace expression
+} // namespace style
+} // namespace mbgl
diff --git a/include/mbgl/style/expression/type.hpp b/include/mbgl/style/expression/type.hpp
new file mode 100644
index 0000000000..d801cd3ac9
--- /dev/null
+++ b/include/mbgl/style/expression/type.hpp
@@ -0,0 +1,111 @@
+#pragma once
+
+#include <mbgl/util/optional.hpp>
+#include <mbgl/util/variant.hpp>
+#include <vector>
+
+namespace mbgl {
+namespace style {
+namespace expression {
+namespace type {
+
+template <class T>
+std::string toString(const T& t);
+
+struct NullType {
+ constexpr NullType() = default;
+ std::string getName() const { return "null"; }
+ bool operator==(const NullType&) const { return true; }
+};
+
+struct NumberType {
+ constexpr NumberType() = default;
+ std::string getName() const { return "number"; }
+ bool operator==(const NumberType&) const { return true; }
+};
+
+struct BooleanType {
+ constexpr BooleanType() = default;
+ std::string getName() const { return "boolean"; }
+ bool operator==(const BooleanType&) const { return true; }
+};
+
+struct StringType {
+ constexpr StringType() = default;
+ std::string getName() const { return "string"; }
+ bool operator==(const StringType&) const { return true; }
+};
+
+struct ColorType {
+ constexpr ColorType() = default;
+ std::string getName() const { return "color"; }
+ bool operator==(const ColorType&) const { return true; }
+};
+
+struct ObjectType {
+ constexpr ObjectType() = default;
+ std::string getName() const { return "object"; }
+ bool operator==(const ObjectType&) const { return true; }
+};
+
+struct ErrorType {
+ constexpr ErrorType() = default;
+ std::string getName() const { return "error"; }
+ bool operator==(const ErrorType&) const { return true; }
+};
+
+struct ValueType {
+ constexpr ValueType() = default;
+ std::string getName() const { return "value"; }
+ bool operator==(const ValueType&) const { return true; }
+};
+
+constexpr NullType Null;
+constexpr NumberType Number;
+constexpr StringType String;
+constexpr BooleanType Boolean;
+constexpr ColorType Color;
+constexpr ValueType Value;
+constexpr ObjectType Object;
+constexpr ErrorType Error;
+
+struct Array;
+
+using Type = variant<
+ NullType,
+ NumberType,
+ BooleanType,
+ StringType,
+ ColorType,
+ ObjectType,
+ ValueType,
+ mapbox::util::recursive_wrapper<Array>,
+ ErrorType>;
+
+struct Array {
+ explicit Array(Type itemType_) : itemType(std::move(itemType_)) {}
+ Array(Type itemType_, std::size_t N_) : itemType(std::move(itemType_)), N(N_) {}
+ Array(Type itemType_, optional<std::size_t> N_) : itemType(std::move(itemType_)), N(std::move(N_)) {}
+ std::string getName() const {
+ if (N) {
+ return "array<" + toString(itemType) + ", " + std::to_string(*N) + ">";
+ } else if (itemType == Value) {
+ return "array";
+ } else {
+ return "array<" + toString(itemType) + ">";
+ }
+ }
+
+ bool operator==(const Array& rhs) const { return itemType == rhs.itemType && N == rhs.N; }
+
+ Type itemType;
+ optional<std::size_t> N;
+};
+
+template <class T>
+std::string toString(const T& type) { return type.match([&] (const auto& t) { return t.getName(); }); }
+
+} // namespace type
+} // namespace expression
+} // namespace style
+} // namespace mbgl
diff --git a/include/mbgl/style/expression/value.hpp b/include/mbgl/style/expression/value.hpp
new file mode 100644
index 0000000000..8baa9d2dba
--- /dev/null
+++ b/include/mbgl/style/expression/value.hpp
@@ -0,0 +1,153 @@
+#pragma once
+
+#include <mbgl/style/expression/type.hpp>
+#include <mbgl/style/position.hpp>
+#include <mbgl/style/types.hpp>
+#include <mbgl/util/color.hpp>
+#include <mbgl/util/enum.hpp>
+#include <mbgl/util/feature.hpp>
+#include <mbgl/util/variant.hpp>
+
+#include <array>
+#include <vector>
+
+namespace mbgl {
+namespace style {
+namespace expression {
+
+struct Value;
+
+using ValueBase = variant<
+ NullValue,
+ bool,
+ double,
+ std::string,
+ Color,
+ mapbox::util::recursive_wrapper<std::vector<Value>>,
+ mapbox::util::recursive_wrapper<std::unordered_map<std::string, Value>>>;
+struct Value : ValueBase {
+ using ValueBase::ValueBase;
+
+ // Javascript's Number.MAX_SAFE_INTEGER
+ static uint64_t maxSafeInteger() { return 9007199254740991ULL; }
+
+ static bool isSafeInteger(uint64_t x) { return x <= maxSafeInteger(); };
+ static bool isSafeInteger(int64_t x) {
+ return static_cast<uint64_t>(x > 0 ? x : -x) <= maxSafeInteger();
+ }
+ static bool isSafeInteger(double x) {
+ return static_cast<uint64_t>(x > 0 ? x : -x) <= maxSafeInteger();
+ }
+
+};
+
+constexpr NullValue Null = NullValue();
+
+type::Type typeOf(const Value& value);
+std::string stringify(const Value& value);
+
+/*
+ Returns a Type object representing the expression type that corresponds to
+ the value type T. (Specialized for primitives and specific array types in
+ the .cpp.)
+*/
+template <typename T>
+type::Type valueTypeToExpressionType();
+
+/*
+ Conversions between style value types and expression::Value
+*/
+
+// no-op overloads
+Value toExpressionValue(const Value&);
+
+// T = Value (just wrap in optional)
+template <typename T>
+std::enable_if_t<std::is_same<T, Value>::value,
+optional<T>> fromExpressionValue(const Value& v)
+{
+ return optional<T>(v);
+}
+
+// T = member type of Value
+template <typename T>
+std::enable_if_t< std::is_convertible<T, Value>::value && !std::is_same<T, Value>::value,
+optional<T>> fromExpressionValue(const Value& v)
+{
+ return v.template is<T>() ? v.template get<T>() : optional<T>();
+}
+
+// real conversions
+template <typename T, typename Enable = std::enable_if_t< !std::is_convertible<T, Value>::value >>
+Value toExpressionValue(const T& value);
+
+template <typename T>
+std::enable_if_t< !std::is_convertible<T, Value>::value,
+optional<T>> fromExpressionValue(const Value& v);
+
+
+
+template <class T, class Enable = void>
+struct ValueConverter {
+ using ExpressionType = T;
+
+ static Value toExpressionValue(const T& value) {
+ return Value(value);
+ }
+ static optional<T> fromExpressionValue(const Value& value) {
+ return value.template is<T>() ? value.template get<T>() : optional<T>();
+ }
+};
+
+template <>
+struct ValueConverter<float> {
+ using ExpressionType = double;
+ static type::Type expressionType() { return type::Number; }
+ static Value toExpressionValue(const float value);
+ static optional<float> fromExpressionValue(const Value& value);
+};
+
+template<>
+struct ValueConverter<mbgl::Value> {
+ static Value toExpressionValue(const mbgl::Value& value);
+};
+
+template <typename T, std::size_t N>
+struct ValueConverter<std::array<T, N>> {
+ using ExpressionType = std::vector<Value>;
+ static type::Type expressionType() {
+ return type::Array(valueTypeToExpressionType<T>(), N);
+ }
+ static Value toExpressionValue(const std::array<T, N>& value);
+ static optional<std::array<T, N>> fromExpressionValue(const Value& value);
+};
+
+template <typename T>
+struct ValueConverter<std::vector<T>> {
+ using ExpressionType = std::vector<Value>;
+ static type::Type expressionType() {
+ return type::Array(valueTypeToExpressionType<T>());
+ }
+ static Value toExpressionValue(const std::vector<T>& value);
+ static optional<std::vector<T>> fromExpressionValue(const Value& value);
+};
+
+template <>
+struct ValueConverter<Position> {
+ using ExpressionType = std::vector<Value>;
+ static type::Type expressionType() { return type::Array(type::Number, 3); }
+ static Value toExpressionValue(const mbgl::style::Position& value);
+ static optional<Position> fromExpressionValue(const Value& v);
+};
+
+template <typename T>
+struct ValueConverter<T, std::enable_if_t< std::is_enum<T>::value >> {
+ using ExpressionType = std::string;
+ static type::Type expressionType() { return type::String; }
+ static Value toExpressionValue(const T& value);
+ static optional<T> fromExpressionValue(const Value& value);
+};
+
+} // namespace expression
+} // namespace style
+} // namespace mbgl
diff --git a/include/mbgl/style/function/camera_function.hpp b/include/mbgl/style/function/camera_function.hpp
index 7fde365b3d..25b38e3616 100644
--- a/include/mbgl/style/function/camera_function.hpp
+++ b/include/mbgl/style/function/camera_function.hpp
@@ -1,10 +1,18 @@
#pragma once
+#include <mbgl/style/expression/expression.hpp>
+#include <mbgl/style/expression/interpolate.hpp>
+#include <mbgl/style/expression/step.hpp>
+#include <mbgl/style/expression/find_zoom_curve.hpp>
+#include <mbgl/style/expression/value.hpp>
+#include <mbgl/style/expression/is_constant.hpp>
+#include <mbgl/style/function/convert.hpp>
#include <mbgl/style/function/exponential_stops.hpp>
#include <mbgl/style/function/interval_stops.hpp>
#include <mbgl/util/interpolate.hpp>
#include <mbgl/util/variant.hpp>
+
namespace mbgl {
namespace style {
@@ -18,24 +26,60 @@ public:
IntervalStops<T>>,
variant<
IntervalStops<T>>>;
+
+ CameraFunction(std::unique_ptr<expression::Expression> expression_)
+ : expression(std::move(expression_)),
+ zoomCurve(expression::findZoomCurveChecked(expression.get()))
+ {
+ assert(!expression::isZoomConstant(*expression));
+ assert(expression::isFeatureConstant(*expression));
+ }
CameraFunction(Stops stops_)
- : stops(std::move(stops_)) {
- }
+ : stops(std::move(stops_)),
+ expression(stops.match([&] (const auto& s) {
+ return expression::Convert::toExpression(s);
+ })),
+ zoomCurve(expression::findZoomCurveChecked(expression.get()))
+ {}
T evaluate(float zoom) const {
- return stops.match([&] (const auto& s) {
- return s.evaluate(zoom).value_or(T());
- });
+ const expression::EvaluationResult result = expression->evaluate(expression::EvaluationContext(zoom, nullptr));
+ if (result) {
+ const optional<T> typed = expression::fromExpressionValue<T>(*result);
+ return typed ? *typed : T();
+ }
+ return T();
+ }
+
+ float interpolationFactor(const Range<float>& inputLevels, const float inputValue) const {
+ return zoomCurve.match(
+ [&](const expression::InterpolateBase* z) {
+ return z->interpolationFactor(Range<double> { inputLevels.min, inputLevels.max }, inputValue);
+ },
+ [&](const expression::Step*) { return 0.0f; }
+ );
+ }
+
+ Range<float> getCoveringStops(const float lower, const float upper) const {
+ return zoomCurve.match(
+ [&](auto z) { return z->getCoveringStops(lower, upper); }
+ );
}
friend bool operator==(const CameraFunction& lhs,
const CameraFunction& rhs) {
- return lhs.stops == rhs.stops;
+ return *lhs.expression == *rhs.expression;
}
- Stops stops;
bool useIntegerZoom = false;
+
+ // retained for compatibility with pre-expression function API
+ Stops stops;
+
+private:
+ std::shared_ptr<expression::Expression> expression;
+ const variant<const expression::InterpolateBase*, const expression::Step*> zoomCurve;
};
} // namespace style
diff --git a/include/mbgl/style/function/composite_function.hpp b/include/mbgl/style/function/composite_function.hpp
index 7b524b6021..b44bf8e6fe 100644
--- a/include/mbgl/style/function/composite_function.hpp
+++ b/include/mbgl/style/function/composite_function.hpp
@@ -1,5 +1,12 @@
#pragma once
+#include <mbgl/style/expression/expression.hpp>
+#include <mbgl/style/expression/interpolate.hpp>
+#include <mbgl/style/expression/step.hpp>
+#include <mbgl/style/expression/find_zoom_curve.hpp>
+#include <mbgl/style/expression/value.hpp>
+#include <mbgl/style/expression/is_constant.hpp>
+#include <mbgl/style/function/convert.hpp>
#include <mbgl/style/function/composite_exponential_stops.hpp>
#include <mbgl/style/function/composite_interval_stops.hpp>
#include <mbgl/style/function/composite_categorical_stops.hpp>
@@ -43,110 +50,71 @@ public:
CompositeIntervalStops<T>,
CompositeCategoricalStops<T>>>;
- CompositeFunction(std::string property_, Stops stops_, optional<T> defaultValue_ = {})
- : property(std::move(property_)),
- stops(std::move(stops_)),
- defaultValue(std::move(defaultValue_)) {
- }
-
- struct CoveringRanges {
- float zoom;
- Range<float> coveringZoomRange;
- Range<InnerStops> coveringStopsRange;
- };
-
- // Return the relevant stop zoom values and inner stops that bracket a given zoom level. This
- // is the first step toward evaluating the function, and is used for in the course of both partial
- // evaluation of data-driven paint properties, and full evaluation of data-driven layout properties.
- CoveringRanges coveringRanges(float zoom) const {
- return stops.match(
- [&] (const auto& s) {
- assert(!s.stops.empty());
- auto minIt = s.stops.lower_bound(zoom);
- auto maxIt = s.stops.upper_bound(zoom);
-
- // lower_bound yields first element >= zoom, but we want the *last*
- // element <= zoom, so if we found a stop > zoom, back up by one.
- if (minIt != s.stops.begin() && minIt != s.stops.end() && minIt->first > zoom) {
- minIt--;
- }
-
- return CoveringRanges {
- zoom,
- Range<float> {
- minIt == s.stops.end() ? s.stops.rbegin()->first : minIt->first,
- maxIt == s.stops.end() ? s.stops.rbegin()->first : maxIt->first
- },
- Range<InnerStops> {
- s.innerStops(minIt == s.stops.end() ? s.stops.rbegin()->second : minIt->second),
- s.innerStops(maxIt == s.stops.end() ? s.stops.rbegin()->second : maxIt->second)
- }
- };
- }
- );
+ CompositeFunction(std::unique_ptr<expression::Expression> expression_)
+ : expression(std::move(expression_)),
+ zoomCurve(expression::findZoomCurveChecked(expression.get()))
+ {
+ assert(!expression::isZoomConstant(*expression));
+ assert(!expression::isFeatureConstant(*expression));
}
- // Given a range of zoom values (typically two adjacent integer zoom levels, e.g. 5.0 and 6.0),
- // return the covering ranges for both. This is used in the course of partial evaluation for
- // data-driven paint properties.
- Range<CoveringRanges> rangeOfCoveringRanges(Range<float> zoomRange) {
- return Range<CoveringRanges> {
- coveringRanges(zoomRange.min),
- coveringRanges(zoomRange.max)
- };
- }
-
- // Given the covering ranges for range of zoom values (typically two adjacent integer zoom levels,
- // e.g. 5.0 and 6.0), and a feature, return the results of fully evaluating the function for that
- // feature at each of the two zoom levels. These two results are what go into the paint vertex buffers
- // for vertices associated with this feature. The shader will interpolate between them at render time.
+ CompositeFunction(std::string property_, Stops stops_, optional<T> defaultValue_ = {})
+ : property(std::move(property_)),
+ stops(std::move(stops_)),
+ defaultValue(std::move(defaultValue_)),
+ expression(stops.match([&] (const auto& s) {
+ return expression::Convert::toExpression(property, s);
+ })),
+ zoomCurve(expression::findZoomCurveChecked(expression.get()))
+ {}
+
+ // Return the range obtained by evaluating the function at each of the zoom levels in zoomRange
template <class Feature>
- Range<T> evaluate(const Range<CoveringRanges>& ranges, const Feature& feature, T finalDefaultValue) {
- optional<Value> value = feature.getValue(property);
- if (!value) {
- return Range<T> {
- defaultValue.value_or(finalDefaultValue),
- defaultValue.value_or(finalDefaultValue)
- };
- }
+ Range<T> evaluate(const Range<float>& zoomRange, const Feature& feature, T finalDefaultValue) {
return Range<T> {
- evaluateFinal(ranges.min, *value, finalDefaultValue),
- evaluateFinal(ranges.max, *value, finalDefaultValue)
+ evaluate(zoomRange.min, feature, finalDefaultValue),
+ evaluate(zoomRange.max, feature, finalDefaultValue)
};
}
- // Fully evaluate the function for a zoom value and feature. This is used when evaluating data-driven
- // layout properties.
template <class Feature>
T evaluate(float zoom, const Feature& feature, T finalDefaultValue) const {
- optional<Value> value = feature.getValue(property);
- if (!value) {
- return defaultValue.value_or(finalDefaultValue);
+ const expression::EvaluationResult result = expression->evaluate(expression::EvaluationContext({zoom}, &feature));
+ if (result) {
+ const optional<T> typed = expression::fromExpressionValue<T>(*result);
+ return typed ? *typed : defaultValue ? *defaultValue : finalDefaultValue;
}
- return evaluateFinal(coveringRanges(zoom), *value, finalDefaultValue);
+ return defaultValue ? *defaultValue : finalDefaultValue;
+ }
+
+ float interpolationFactor(const Range<float>& inputLevels, const float inputValue) const {
+ return zoomCurve.match(
+ [&](const expression::InterpolateBase* z) {
+ return z->interpolationFactor(Range<double> { inputLevels.min, inputLevels.max }, inputValue);
+ },
+ [&](const expression::Step*) { return 0.0f; }
+ );
+ }
+
+ Range<float> getCoveringStops(const float lower, const float upper) const {
+ return zoomCurve.match(
+ [&](auto z) { return z->getCoveringStops(lower, upper); }
+ );
}
friend bool operator==(const CompositeFunction& lhs,
const CompositeFunction& rhs) {
- return std::tie(lhs.property, lhs.stops, lhs.defaultValue)
- == std::tie(rhs.property, rhs.stops, rhs.defaultValue);
+ return *lhs.expression == *rhs.expression;
}
std::string property;
Stops stops;
optional<T> defaultValue;
bool useIntegerZoom = false;
-
+
private:
- T evaluateFinal(const CoveringRanges& ranges, const Value& value, T finalDefaultValue) const {
- auto eval = [&] (const auto& s) {
- return s.evaluate(value).value_or(defaultValue.value_or(finalDefaultValue));
- };
- return util::interpolate(
- ranges.coveringStopsRange.min.match(eval),
- ranges.coveringStopsRange.max.match(eval),
- util::interpolationFactor(1.0f, ranges.coveringZoomRange, ranges.zoom));
- }
+ std::shared_ptr<expression::Expression> expression;
+ const variant<const expression::InterpolateBase*, const expression::Step*> zoomCurve;
};
} // namespace style
diff --git a/include/mbgl/style/function/convert.hpp b/include/mbgl/style/function/convert.hpp
new file mode 100644
index 0000000000..ed35b4bf14
--- /dev/null
+++ b/include/mbgl/style/function/convert.hpp
@@ -0,0 +1,351 @@
+#pragma once
+
+#include <mbgl/style/expression/array_assertion.hpp>
+#include <mbgl/style/expression/assertion.hpp>
+#include <mbgl/style/expression/case.hpp>
+#include <mbgl/style/expression/coalesce.hpp>
+#include <mbgl/style/expression/compound_expression.hpp>
+#include <mbgl/style/expression/coercion.hpp>
+#include <mbgl/style/expression/interpolate.hpp>
+#include <mbgl/style/expression/expression.hpp>
+#include <mbgl/style/expression/literal.hpp>
+#include <mbgl/style/expression/match.hpp>
+#include <mbgl/style/expression/step.hpp>
+
+#include <mbgl/style/function/exponential_stops.hpp>
+#include <mbgl/style/function/interval_stops.hpp>
+#include <mbgl/style/function/categorical_stops.hpp>
+#include <mbgl/style/function/composite_exponential_stops.hpp>
+#include <mbgl/style/function/composite_interval_stops.hpp>
+#include <mbgl/style/function/composite_categorical_stops.hpp>
+#include <mbgl/style/function/identity_stops.hpp>
+
+#include <mbgl/util/enum.hpp>
+#include <mbgl/style/types.hpp>
+
+#include <string>
+
+
+namespace mbgl {
+namespace style {
+namespace expression {
+
+namespace detail {
+
+class ErrorExpression : public Expression {
+public:
+ ErrorExpression(std::string message_) : Expression(type::Error), message(std::move(message_)) {}
+ void eachChild(const std::function<void(const Expression&)>&) const override {}
+
+ bool operator==(const Expression& e) const override {
+ return dynamic_cast<const ErrorExpression*>(&e);
+ }
+
+ EvaluationResult evaluate(const EvaluationContext&) const override {
+ return EvaluationError{message};
+ }
+
+private:
+ std::string message;
+};
+
+} // namespace detail
+
+
+// Create expressions representing 'classic' (i.e. stop-based) style functions
+
+struct Convert {
+ template <typename T>
+ static std::unique_ptr<Literal> makeLiteral(const T& value) {
+ return std::make_unique<Literal>(Value(toExpressionValue(value)));
+ }
+
+ static std::unique_ptr<Expression> makeGet(type::Type type, const std::string& property) {
+ ParsingContext ctx;
+ std::vector<std::unique_ptr<Expression>> getArgs;
+ getArgs.push_back(makeLiteral(property));
+ ParseResult get = createCompoundExpression("get", std::move(getArgs), ctx);
+ assert(get);
+ assert(ctx.getErrors().size() == 0);
+
+ std::vector<std::unique_ptr<Expression>> assertionArgs;
+ assertionArgs.push_back(std::move(*get));
+
+ return std::make_unique<Assertion>(type, std::move(assertionArgs));
+ }
+
+ static std::unique_ptr<Expression> makeZoom() {
+ ParsingContext ctx;
+ ParseResult zoom = createCompoundExpression("zoom", std::vector<std::unique_ptr<Expression>>(), ctx);
+ assert(zoom);
+ assert(ctx.getErrors().size() == 0);
+ return std::move(*zoom);
+ }
+
+ static std::unique_ptr<Expression> makeError(std::string message) {
+ return std::make_unique<detail::ErrorExpression>(message);
+ }
+
+ template <typename OutputType>
+ static ParseResult makeInterpolate(type::Type type,
+ std::unique_ptr<Expression> input,
+ std::map<double, std::unique_ptr<Expression>> convertedStops,
+ typename Interpolate<OutputType>::Interpolator interpolator)
+ {
+ ParseResult curve = ParseResult(std::make_unique<Interpolate<OutputType>>(
+ std::move(type),
+ std::move(interpolator),
+ std::move(input),
+ std::move(convertedStops)
+ ));
+ assert(curve);
+ return std::move(*curve);
+ }
+
+ template <typename Key>
+ static ParseResult makeMatch(type::Type type,
+ std::unique_ptr<Expression> input,
+ std::map<CategoricalValue, std::unique_ptr<Expression>> stops) {
+ // match expression
+ typename Match<Key>::Branches branches;
+ for(auto it = stops.begin(); it != stops.end(); it++) {
+ assert(it->first.template is<Key>());
+ Key key = it->first.template get<Key>();
+ branches.emplace(
+ std::move(key),
+ std::move(it->second)
+ );
+ }
+
+ return ParseResult(std::make_unique<Match<Key>>(std::move(type),
+ std::move(input),
+ std::move(branches),
+ makeError("No matching label")));
+ }
+
+ static ParseResult makeCase(type::Type type,
+ std::unique_ptr<Expression> input,
+ std::map<CategoricalValue, std::unique_ptr<Expression>> stops) {
+ // case expression
+ std::vector<typename Case::Branch> branches;
+
+ auto it = stops.find(true);
+ std::unique_ptr<Expression> true_case = it == stops.end() ?
+ makeError("No matching label") :
+ std::move(it->second);
+
+ it = stops.find(false);
+ std::unique_ptr<Expression> false_case = it == stops.end() ?
+ makeError("No matching label") :
+ std::move(it->second);
+
+ branches.push_back(std::make_pair(std::move(input), std::move(true_case)));
+ return ParseResult(std::make_unique<Case>(std::move(type), std::move(branches), std::move(false_case)));
+ }
+
+ template <typename T>
+ static ParseResult fromCategoricalStops(std::map<CategoricalValue, T> stops, const std::string& property) {
+ assert(stops.size() > 0);
+
+ std::map<CategoricalValue, std::unique_ptr<Expression>> convertedStops;
+ for(const std::pair<CategoricalValue, T>& stop : stops) {
+ convertedStops.emplace(
+ stop.first,
+ makeLiteral(stop.second)
+ );
+ }
+
+ type::Type type = valueTypeToExpressionType<T>();
+
+ const CategoricalValue& firstKey = stops.begin()->first;
+ return firstKey.match(
+ [&](bool) {
+ return makeCase(type, makeGet(type::Boolean, property), std::move(convertedStops));
+ },
+ [&](const std::string&) {
+ return makeMatch<std::string>(type, makeGet(type::String, property), std::move(convertedStops));
+ },
+ [&](int64_t) {
+ return makeMatch<int64_t>(type, makeGet(type::Number, property), std::move(convertedStops));
+ }
+ );
+ }
+
+ template <typename T>
+ static std::map<double, std::unique_ptr<Expression>> convertStops(const std::map<float, T>& stops) {
+ std::map<double, std::unique_ptr<Expression>> convertedStops;
+ for(const std::pair<float, T>& stop : stops) {
+ convertedStops.emplace(
+ stop.first,
+ makeLiteral(stop.second)
+ );
+ }
+ return convertedStops;
+ }
+
+ template <typename T>
+ static std::unique_ptr<Expression> toExpression(const ExponentialStops<T>& stops)
+ {
+ ParseResult e = makeInterpolate<typename ValueConverter<T>::ExpressionType>(
+ valueTypeToExpressionType<T>(),
+ makeZoom(),
+ convertStops(stops.stops),
+ ExponentialInterpolator(stops.base));
+ assert(e);
+ return std::move(*e);
+ }
+
+ template <typename T>
+ static std::unique_ptr<Expression> toExpression(const IntervalStops<T>& stops)
+ {
+ ParseResult e(std::make_unique<Step>(valueTypeToExpressionType<T>(),
+ makeZoom(),
+ convertStops(stops.stops)));
+ assert(e);
+ return std::move(*e);
+ }
+
+ template <typename T>
+ static std::unique_ptr<Expression> toExpression(const std::string& property,
+ const ExponentialStops<T>& stops)
+ {
+ ParseResult e = makeInterpolate<typename ValueConverter<T>::ExpressionType>(valueTypeToExpressionType<T>(),
+ makeGet(type::Number, property),
+ convertStops(stops.stops),
+ ExponentialInterpolator(stops.base));
+ assert(e);
+ return std::move(*e);
+ }
+
+ template <typename T>
+ static std::unique_ptr<Expression> toExpression(const std::string& property,
+ const IntervalStops<T>& stops)
+ {
+ std::unique_ptr<Expression> get = makeGet(type::Number, property);
+ ParseResult e(std::make_unique<Step>(valueTypeToExpressionType<T>(),
+ std::move(get),
+ convertStops(stops.stops)));
+ assert(e);
+ return std::move(*e);
+ }
+
+ template <typename T>
+ static std::unique_ptr<Expression> toExpression(const std::string& property,
+ const CategoricalStops<T>& stops)
+ {
+ ParseResult expr = fromCategoricalStops(stops.stops, property);
+ assert(expr);
+ return std::move(*expr);
+ }
+
+ // interpolatable zoom curve
+ template <typename T>
+ static typename std::enable_if_t<util::Interpolatable<T>::value,
+ ParseResult> makeZoomCurve(std::map<double, std::unique_ptr<Expression>> stops) {
+ return makeInterpolate<typename ValueConverter<T>::ExpressionType>(valueTypeToExpressionType<T>(),
+ makeZoom(),
+ std::move(stops),
+ ExponentialInterpolator(1.0));
+ }
+
+ // non-interpolatable zoom curve
+ template <typename T>
+ static typename std::enable_if_t<!util::Interpolatable<T>::value,
+ ParseResult> makeZoomCurve(std::map<double, std::unique_ptr<Expression>> stops) {
+ return ParseResult(std::make_unique<Step>(valueTypeToExpressionType<T>(), makeZoom(), std::move(stops)));
+ }
+
+ template <typename T>
+ static std::unique_ptr<Expression> toExpression(const std::string& property,
+ const CompositeExponentialStops<T>& stops)
+ {
+ std::map<double, std::unique_ptr<Expression>> outerStops;
+ for (const std::pair<float, std::map<float, T>>& stop : stops.stops) {
+ std::unique_ptr<Expression> get = makeGet(type::Number, property);
+ ParseResult innerInterpolate = makeInterpolate<typename ValueConverter<T>::ExpressionType>(valueTypeToExpressionType<T>(),
+ std::move(get),
+ convertStops(stop.second),
+ ExponentialInterpolator(stops.base));
+ assert(innerInterpolate);
+ outerStops.emplace(stop.first, std::move(*innerInterpolate));
+ }
+
+ ParseResult zoomCurve = makeZoomCurve<T>(std::move(outerStops));
+ assert(zoomCurve);
+ return std::move(*zoomCurve);
+ }
+
+ template <typename T>
+ static std::unique_ptr<Expression> toExpression(const std::string& property,
+ const CompositeIntervalStops<T>& stops)
+ {
+ std::map<double, std::unique_ptr<Expression>> outerStops;
+ for (const std::pair<float, std::map<float, T>>& stop : stops.stops) {
+ std::unique_ptr<Expression> get = makeGet(type::Number, property);
+ ParseResult innerInterpolate(std::make_unique<Step>(valueTypeToExpressionType<T>(),
+ std::move(get),
+ convertStops(stop.second)));
+ assert(innerInterpolate);
+ outerStops.emplace(stop.first, std::move(*innerInterpolate));
+ }
+
+ ParseResult zoomCurve = makeZoomCurve<T>(std::move(outerStops));
+ assert(zoomCurve);
+ return std::move(*zoomCurve);
+ }
+
+ template <typename T>
+ static std::unique_ptr<Expression> toExpression(const std::string& property,
+ const CompositeCategoricalStops<T>& stops)
+ {
+ std::map<double, std::unique_ptr<Expression>> outerStops;
+ for (const std::pair<float, std::map<CategoricalValue, T>>& stop : stops.stops) {
+ ParseResult innerInterpolate = fromCategoricalStops(stop.second, property);
+ assert(innerInterpolate);
+ outerStops.emplace(stop.first, std::move(*innerInterpolate));
+ }
+
+ ParseResult zoomCurve = makeZoomCurve<T>(std::move(outerStops));
+ assert(zoomCurve);
+ return std::move(*zoomCurve);
+ }
+
+
+ static std::unique_ptr<Expression> fromIdentityFunction(type::Type type, const std::string& property)
+ {
+ std::unique_ptr<Expression> input = type.match(
+ [&] (const type::StringType&) {
+ return makeGet(type::String, property);
+ },
+ [&] (const type::NumberType&) {
+ return makeGet(type::Number, property);
+ },
+ [&] (const type::BooleanType&) {
+ return makeGet(type::Boolean, property);
+ },
+ [&] (const type::ColorType&) {
+ std::vector<std::unique_ptr<Expression>> args;
+ args.push_back(makeGet(type::String, property));
+ return std::make_unique<Coercion>(type::Color, std::move(args));
+ },
+ [&] (const type::Array& arr) {
+ std::vector<std::unique_ptr<Expression>> getArgs;
+ getArgs.push_back(makeLiteral(property));
+ ParsingContext ctx;
+ ParseResult get = createCompoundExpression("get", std::move(getArgs), ctx);
+ assert(get);
+ assert(ctx.getErrors().size() == 0);
+ return std::make_unique<ArrayAssertion>(arr, std::move(*get));
+ },
+ [&] (const auto&) -> std::unique_ptr<Expression> {
+ return makeLiteral(Null);
+ }
+ );
+
+ return input;
+ }
+};
+
+} // namespace expression
+} // namespace style
+} // namespace mbgl
diff --git a/include/mbgl/style/function/source_function.hpp b/include/mbgl/style/function/source_function.hpp
index 9c2ad101ec..02e4b604e2 100644
--- a/include/mbgl/style/function/source_function.hpp
+++ b/include/mbgl/style/function/source_function.hpp
@@ -1,5 +1,7 @@
#pragma once
+#include <mbgl/style/expression/is_constant.hpp>
+#include <mbgl/style/function/convert.hpp>
#include <mbgl/style/function/exponential_stops.hpp>
#include <mbgl/style/function/interval_stops.hpp>
#include <mbgl/style/function/categorical_stops.hpp>
@@ -27,33 +29,48 @@ public:
CategoricalStops<T>,
IdentityStops<T>>>;
+ SourceFunction(std::unique_ptr<expression::Expression> expression_)
+ : expression(std::move(expression_))
+ {
+ assert(expression::isZoomConstant(*expression));
+ assert(!expression::isFeatureConstant(*expression));
+ }
+
SourceFunction(std::string property_, Stops stops_, optional<T> defaultValue_ = {})
: property(std::move(property_)),
stops(std::move(stops_)),
- defaultValue(std::move(defaultValue_)) {
- }
+ defaultValue(std::move(defaultValue_)),
+ expression(stops.match([&] (const IdentityStops<T>&) {
+ return expression::Convert::fromIdentityFunction(expression::valueTypeToExpressionType<T>(), property);
+ }, [&] (const auto& s) {
+ return expression::Convert::toExpression(property, s);
+ }))
+ {}
template <class Feature>
T evaluate(const Feature& feature, T finalDefaultValue) const {
- optional<Value> v = feature.getValue(property);
- if (!v) {
- return defaultValue.value_or(finalDefaultValue);
+ const expression::EvaluationResult result = expression->evaluate(expression::EvaluationContext(&feature));
+ if (result) {
+ const optional<T> typed = expression::fromExpressionValue<T>(*result);
+ return typed ? *typed : defaultValue ? *defaultValue : finalDefaultValue;
}
- return stops.match([&] (const auto& s) -> T {
- return s.evaluate(*v).value_or(defaultValue.value_or(finalDefaultValue));
- });
+ return defaultValue ? *defaultValue : finalDefaultValue;
}
friend bool operator==(const SourceFunction& lhs,
const SourceFunction& rhs) {
- return std::tie(lhs.property, lhs.stops, lhs.defaultValue)
- == std::tie(rhs.property, rhs.stops, rhs.defaultValue);
+ return *lhs.expression == *rhs.expression;
}
+ bool useIntegerZoom = false;
+
+ // retained for compatibility with pre-expression function API
std::string property;
Stops stops;
optional<T> defaultValue;
- bool useIntegerZoom = false;
+
+private:
+ std::shared_ptr<expression::Expression> expression;
};
} // namespace style
diff --git a/include/mbgl/style/types.hpp b/include/mbgl/style/types.hpp
index ec7358de8c..2ed95f08b8 100644
--- a/include/mbgl/style/types.hpp
+++ b/include/mbgl/style/types.hpp
@@ -4,7 +4,9 @@
namespace mbgl {
-// TODO: should be in public source.hpp header and style namespace
+namespace style {
+
+// TODO: should be in public source.hpp header
enum class SourceType : uint8_t {
Vector,
Raster,
@@ -14,8 +16,6 @@ enum class SourceType : uint8_t {
Image
};
-namespace style {
-
enum class VisibilityType : bool {
Visible,
None,
diff --git a/include/mbgl/util/enum.hpp b/include/mbgl/util/enum.hpp
index 369ca86bfd..608befd3c4 100644
--- a/include/mbgl/util/enum.hpp
+++ b/include/mbgl/util/enum.hpp
@@ -11,6 +11,7 @@ namespace mbgl {
template <typename T>
class Enum {
public:
+ using Type = T;
static const char * toString(T);
static optional<T> toEnum(const std::string&);
};
diff --git a/include/mbgl/util/geo.hpp b/include/mbgl/util/geo.hpp
index 6d725b102b..54a8c99fab 100644
--- a/include/mbgl/util/geo.hpp
+++ b/include/mbgl/util/geo.hpp
@@ -161,6 +161,13 @@ public:
point.longitude() <= ne.longitude());
}
+ bool contains(const LatLngBounds& area) const {
+ return (area.ne.latitude() <= ne.latitude() &&
+ area.sw.latitude() >= sw.latitude() &&
+ area.ne.longitude() <= ne.longitude() &&
+ area.sw.longitude() >= sw.longitude());
+ }
+
bool intersects(const LatLngBounds area) const {
return (area.ne.latitude() > sw.latitude() &&
area.sw.latitude() < ne.latitude() &&
diff --git a/include/mbgl/util/interpolate.hpp b/include/mbgl/util/interpolate.hpp
index 6738987598..aff730a0a2 100644
--- a/include/mbgl/util/interpolate.hpp
+++ b/include/mbgl/util/interpolate.hpp
@@ -3,6 +3,7 @@
#include <mbgl/util/color.hpp>
#include <mbgl/util/range.hpp>
#include <mbgl/style/position.hpp>
+#include <mbgl/style/expression/value.hpp>
#include <array>
#include <vector>
@@ -47,6 +48,36 @@ public:
}
};
+
+// In order to accept Array<Number, N> as an output value for Curve
+// expressions, we need to have an interpolatable std::vector type.
+// However, style properties like line-dasharray are represented using
+// std::vector<float>, and should NOT be considered interpolatable.
+// So, we use std::vector<Value> to represent expression array values,
+// asserting that (a) the vectors are the same size, and (b) they contain
+// only numeric values. (These invariants should be relatively safe,
+// being enforced by the expression type system.)
+template<>
+struct Interpolator<std::vector<style::expression::Value>> {
+ std::vector<style::expression::Value> operator()(const std::vector<style::expression::Value>& a,
+ const std::vector<style::expression::Value>& b,
+ const double t) const {
+ assert(a.size() == b.size());
+ if (a.size() == 0) return {};
+ std::vector<style::expression::Value> result;
+ for (std::size_t i = 0; i < a.size(); i++) {
+ assert(a[i].template is<double>());
+ assert(b[i].template is<double>());
+ style::expression::Value item = interpolate(
+ a[i].template get<double>(),
+ b[i].template get<double>(),
+ t);
+ result.push_back(item);
+ }
+ return result;
+ }
+};
+
template <>
struct Interpolator<style::Position> {
public:
@@ -101,5 +132,7 @@ struct Interpolatable
std::true_type,
std::false_type> {};
+
+
} // namespace util
} // namespace mbgl
diff --git a/include/mbgl/util/projection.hpp b/include/mbgl/util/projection.hpp
index f64502c5bc..1613af3b36 100644
--- a/include/mbgl/util/projection.hpp
+++ b/include/mbgl/util/projection.hpp
@@ -92,8 +92,8 @@ public:
const double t2z = tileSize * std::pow(2, zoom);
Point<double> pt = project_(point, t2z);
// Flip y coordinate
- auto x = std::round(std::min(pt.x, t2z));
- auto y = std::round(std::min(t2z - pt.y, t2z));
+ auto x = ::round(std::min(pt.x, t2z));
+ auto y = ::round(std::min(t2z - pt.y, t2z));
return { x, y };
}
private:
diff --git a/include/mbgl/util/tileset.hpp b/include/mbgl/util/tileset.hpp
index 61aa47d4ea..5a03e1a9da 100644
--- a/include/mbgl/util/tileset.hpp
+++ b/include/mbgl/util/tileset.hpp
@@ -3,6 +3,7 @@
#include <mbgl/util/range.hpp>
#include <mbgl/util/constants.hpp>
+#include <tuple>
#include <vector>
#include <string>
#include <cstdint>
diff --git a/include/mbgl/util/unitbezier.hpp b/include/mbgl/util/unitbezier.hpp
index 6e644e2d1f..92f23d6718 100644
--- a/include/mbgl/util/unitbezier.hpp
+++ b/include/mbgl/util/unitbezier.hpp
@@ -26,6 +26,7 @@
#pragma once
#include <cmath>
+#include <tuple>
namespace mbgl {
namespace util {
@@ -102,6 +103,11 @@ struct UnitBezier {
double solve(double x, double epsilon) const {
return sampleCurveY(solveCurveX(x, epsilon));
}
+
+ bool operator==(const UnitBezier& rhs) const {
+ return std::tie(cx, bx, ax, cy, by, ay) ==
+ std::tie(rhs.cx, rhs.bx, rhs.ax, rhs.cy, rhs.by, rhs.ay);
+ }
private:
const double cx;
diff --git a/mapbox-gl-js b/mapbox-gl-js
-Subproject 3634d945477cb7dbbf1e0bebf4cf51542821895
+Subproject 3f419f30b99271cf05c269c8cd40d5c94811953
diff --git a/package.json b/package.json
index fb62bc63ed..4ff4b32735 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@mapbox/mapbox-gl-native",
- "version": "3.5.6",
+ "version": "3.5.8",
"description": "Renders map tiles with Mapbox GL",
"keywords": [
"mapbox",
@@ -13,8 +13,8 @@
},
"license": "BSD-2-Clause",
"dependencies": {
- "nan": "^2.4.0",
- "node-pre-gyp": "^0.6.36",
+ "nan": "^2.6.2",
+ "node-pre-gyp": "^0.6.37",
"npm-run-all": "^4.0.2"
},
"devDependencies": {
@@ -23,6 +23,7 @@
"ejs": "^2.4.1",
"express": "^4.11.1",
"flow-remove-types": "^1.2.1",
+ "json-stringify-pretty-compact": "^1.0.4",
"lodash": "^4.16.4",
"mapbox-gl-styles": "2.0.2",
"pixelmatch": "^4.0.2",
@@ -38,7 +39,8 @@
"install": "node-pre-gyp install --fallback-to-build=false || make node",
"test": "tape platform/node/test/js/**/*.test.js",
"test-memory": "node --expose-gc platform/node/test/memory.test.js",
- "test-suite": "run-s test-render test-query",
+ "test-suite": "run-s test-render test-query test-expressions",
+ "test-expressions": "node platform/node/test/expression.test.js",
"test-render": "node platform/node/test/render.test.js",
"test-query": "node platform/node/test/query.test.js"
},
diff --git a/platform/android/CHANGELOG.md b/platform/android/CHANGELOG.md
index d61c2ec8b9..e1bb8fb456 100644
--- a/platform/android/CHANGELOG.md
+++ b/platform/android/CHANGELOG.md
@@ -4,8 +4,6 @@ Mapbox welcomes participation and contributions from everyone. If you'd like to
## 5.2.0 - TBA
-* TBA
-
## 5.2.0-beta.4 - November 3, 2017
- Revert adding mapbox-android-core dependency (#10354) [#10380](https://github.com/mapbox/mapbox-gl-native/pull/10380)
@@ -31,9 +29,9 @@ Mapbox welcomes participation and contributions from everyone. If you'd like to
## 5.2.0-beta.3 - October 26, 2017
-- Reorganize dependencies [#10268](https://github.com/mapbox/mapbox-gl-native/pull/10268)
-- Blacklist VAO usage on adreno 3xx [#10291](https://github.com/mapbox/mapbox-gl-native/pull/10291)
-- On stop null check [#10259](https://github.com/mapbox/mapbox-gl-native/pull/10259)
+- Reorganize dependencies [#10268](https://github.com/mapbox/mapbox-gl-native/pull/10268)
+- Blacklist VAO usage on adreno 3xx [#10291](https://github.com/mapbox/mapbox-gl-native/pull/10291)
+- On stop null check [#10259](https://github.com/mapbox/mapbox-gl-native/pull/10259)
## 5.2.0-beta.2 - October 19, 2017
@@ -50,9 +48,9 @@ Mapbox welcomes participation and contributions from everyone. If you'd like to
## 5.2.0-beta.1 - October 6, 2017
-* Allow multiple listeners for camera events, deprecate old API [#10141](https://github.com/mapbox/mapbox-gl-native/pull/10141)
-* Update symbol layer example with location [#10092](https://github.com/mapbox/mapbox-gl-native/pull/10092)
-* Make OfflineTilePyramidRegionDefinition parceable [#10080](https://github.com/mapbox/mapbox-gl-native/pull/10080)
+- Allow multiple listeners for camera events, deprecate old API [#10141](https://github.com/mapbox/mapbox-gl-native/pull/10141)
+- Update symbol layer example with location [#10092](https://github.com/mapbox/mapbox-gl-native/pull/10092)
+- Make OfflineTilePyramidRegionDefinition parceable [#10080](https://github.com/mapbox/mapbox-gl-native/pull/10080)
- Fix 5.2.0-SNAPSHOT CI build failing [#10079](https://github.com/mapbox/mapbox-gl-native/pull/10079)
- Deprecate MarkerView [#9782](https://github.com/mapbox/mapbox-gl-native/pull/9782)
- Hide overlain views on initalisation [#10068](https://github.com/mapbox/mapbox-gl-native/pull/10068)
@@ -99,10 +97,17 @@ Mapbox welcomes participation and contributions from everyone. If you'd like to
- Fix javadoc comment for public setOfflineMapboxTileCountLimit method [#9454](https://github.com/mapbox/mapbox-gl-native/pull/9454)
- add Map change & visibility test activities [#9425](https://github.com/mapbox/mapbox-gl-native/pull/9425)
- build release package once during ci build [#9351](https://github.com/mapbox/mapbox-gl-native/pull/9351)
-* Add support for ImageSource [#9110](https://github.com/mapbox/mapbox-gl-native/pull/9110)
-* Increased the default maximum zoom level from 20 to 22. [#9835](https://github.com/mapbox/mapbox-gl-native/pull/9835)
+- Add support for ImageSource [#9110](https://github.com/mapbox/mapbox-gl-native/pull/9110)
+- Increased the default maximum zoom level from 20 to 22. [#9835](https://github.com/mapbox/mapbox-gl-native/pull/9835)
+
+## 5.1.5 - October 31, 2017
+
+* Remove obsolete terminate context/display calls [#10162](https://github.com/mapbox/mapbox-gl-native/pull/10162)
+* Determine need for clip ID based on actual layers/tiles [#10216](https://github.com/mapbox/mapbox-gl-native/pull/10216)
+* Correctly alter sprite URLs [#10217](https://github.com/mapbox/mapbox-gl-native/pull/10217)
+* Russian and Ukrainian localizations [#9945](https://github.com/mapbox/mapbox-gl-native/pull/9945)
-### 5.1.4 - September 25, 2017
+## 5.1.4 - September 25, 2017
* Update translations [#10033](https://github.com/mapbox/mapbox-gl-native/pull/10033) & [#9945](https://github.com/mapbox/mapbox-gl-native/pull/9945)
* Continue rendering tiles despite erros [#10012](https://github.com/mapbox/mapbox-gl-native/pull/10012)
@@ -597,12 +602,6 @@ Mapbox Android 4.0.0 is the most ambitious Android release to date with 3 major
- Satellite Streets Style ([#2739](https://github.com/mapbox/mapbox-gl-native/issues/2739))
- **RESOLVED** Black Screen On Ice Cream Sandwich and Jelly Bean devices ([#2802](https://github.com/mapbox/mapbox-gl-native/issues/2802))
-
## 2.1.0 - October 21, 2015
-- Initial Android release.
-
-Known issues:
-
-- Black Screen On Ice Cream Sandwich and Jelly Bean devices ([#2802](https://github.com/mapbox/mapbox-gl-native/issues/2802))
- - Resolved in 2.2.0
+- Initial Android release. \ No newline at end of file
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java
index c025a119b7..bcfaed2858 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
@@ -28,8 +28,9 @@ import com.mapbox.mapboxsdk.annotations.Annotation;
import com.mapbox.mapboxsdk.annotations.MarkerViewManager;
import com.mapbox.mapboxsdk.constants.MapboxConstants;
import com.mapbox.mapboxsdk.constants.Style;
-import com.mapbox.mapboxsdk.maps.renderer.MapRenderer;
import com.mapbox.mapboxsdk.maps.renderer.glsurfaceview.GLSurfaceViewMapRenderer;
+import com.mapbox.mapboxsdk.maps.renderer.MapRenderer;
+
import com.mapbox.mapboxsdk.maps.renderer.textureview.TextureViewMapRenderer;
import com.mapbox.mapboxsdk.maps.widgets.CompassView;
import com.mapbox.mapboxsdk.maps.widgets.MyLocationView;
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/snapshot/MapSnapshotterMarkerActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/snapshot/MapSnapshotterMarkerActivity.java
index 582d4a29c5..781e7b6334 100644
--- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/snapshot/MapSnapshotterMarkerActivity.java
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/snapshot/MapSnapshotterMarkerActivity.java
@@ -25,6 +25,8 @@ import timber.log.Timber;
*/
public class MapSnapshotterMarkerActivity extends AppCompatActivity implements MapSnapshotter.SnapshotReadyCallback {
+ private MapSnapshotter mapSnapshotter;
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -40,7 +42,7 @@ public class MapSnapshotterMarkerActivity extends AppCompatActivity implements M
Timber.i("Starting snapshot");
- MapSnapshotter mapSnapshotter = new MapSnapshotter(
+ mapSnapshotter = new MapSnapshotter(
getApplicationContext(),
new MapSnapshotter
.Options(Math.min(container.getMeasuredWidth(), 1024), Math.min(container.getMeasuredHeight(), 1024))
diff --git a/platform/android/README.md b/platform/android/README.md
index bd95bdf7fa..d8ec069c6d 100644
--- a/platform/android/README.md
+++ b/platform/android/README.md
@@ -2,15 +2,15 @@
[![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)
-A library based on [Mapbox GL Native](../../README.md) for embedding interactive map views with scalable, customizable vector maps into Java applications on Android devices.
+A library based on [Mapbox GL Native](../../README.md) for embedding interactive map views with scalable, customizable vector maps onto Android devices.
-## Getting Started
+## Getting Started
Alright. So, actually, you may be in the wrong place. From here on in, this README is going to be for people who are interested in working on and improving on Mapbox GL Native for Android.
**To view our current API documentation, see our [JavaDoc](https://www.mapbox.com/android-sdk/api).**
-**To install and use the Mapbox Android SDK in an application, see the [Mapbox Android SDK website](https://www.mapbox.com/android-sdk/).**
+**To install and use the Mapbox Android SDK in an application, see the [Mapbox Android SDK website](https://www.mapbox.com/install/android/).**
[![](https://www.mapbox.com/android-sdk/images/splash.png)](https://www.mapbox.com/android-sdk/)
@@ -23,79 +23,89 @@ Alright. So, actually, you may be in the wrong place. From here on in, this READ
Clone the git repository
```bash
-git clone https://github.com/mapbox/mapbox-gl-native.git
-cd mapbox-gl-native
+git clone git@github.com:mapbox/mapbox-gl-native.git && cd mapbox-gl-native
```
#### Installing dependencies
These dependencies are required for all operating systems and all platform targets.
-- Latest stable [Android Studio](https://developer.android.com/studio/index.html)
+- Latest stable [Android Studio](https://developer.android.com/studio/index.html)
- Update Android SDK with latest
- - Android SDK Build-Tools
+ - Android SDK Build-Tools
- Android Platform-Tools
- Android SDK Tools
- CMake
- NDK
- LLDB
-
- Modern C++ compiler that supports `-std=c++14`\*
- clang++ 3.5 or later or
- g++-4.9 or later
-- [cURL](https://curl.haxx.se) (for build only)
-- [Node.js](https://nodejs.org/) or later (for build only)
-- [pkg-config](https://wiki.freedesktop.org/www/Software/pkg-config/) (for build only)
+- [Node.js](https://nodejs.org/)
+- [ccache](https://ccache.samba.org/) (optional)
**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).
-##### Additional Dependencies for Linux
+**Note**: On macOS you can install clang with installing the [Apple command line developer tools](https://developer.apple.com/download/).
+
+### Opening the project
-_These instructions were tested on Ubuntu 16.04 LTS (aka Xenial Xerus)._
+#### macOS
+
+Execute the following command in this repository's root folder to generate the required build files and open the project with Android Studio:
```
-$ sudo apt-get install -y build-essential curl lib32stdc++6 lib32z1 pkg-config python
+make aproj
```
-##### Additional Dependencies for macOS
+#### linux
-- Apple Command Line Tools (available at [Apple Developer](https://developer.apple.com/download/more/))
-- [xcpretty](https://github.com/supermarin/xcpretty) (`gem install xcpretty`)
+run `make android-configuration` in the root folder of the project and open the Android Studio project in `/platform/android`.
+### Project configuration
-#### Open project in Android Studio
+#### Setup Checkstyle
-##### macOS
+Mapbox uses specific IDE settings related to code and check style.
+See [checkstyle guide](https://github.com/mapbox/mapbox-gl-native/wiki/Setting-up-Mapbox-checkstyle) for configuration details.
-Execute the following to generate the required build files and open the project with Android Studio:
-
-```
-make aproj
-```
+##### Setting Mapbox Access Token
-##### linux
+_The test application (used for development purposes) uses Mapbox vector tiles, which require a Mapbox account and API access token. Obtain a free access token on the [Mapbox account page](https://www.mapbox.com/studio/account/tokens/)._
-Open Android Studio project in `/platform/android`, run `make android-configuration` in the root folder of the project.
+With the first gradle invocation, gradle will take the value of the `MAPBOX_ACCESS_TOKEN` environment variable and save it to `MapboxGLAndroidSDKTestApp/src/main/res/values/developer-config.xml`. If the environment variable wasn't set, you can edit `developer-config.xml` manually and add your access token to the `mapbox_access_token` resource.
-##### Setup Checkstyle
+### Running project
-Mapbox uses specific IDE settings related to code and check style.
-See [checkstyle guide](https://github.com/mapbox/mapbox-gl-native/wiki/Setting-up-Mapbox-checkstyle) for configuration details.
+Run the configuration for the `MapboxGLAndroidSDKTestApp` module and select a device or emulator to deploy on. Based on the selected device, the c++ code will be compiled for the related processor architecture. You can see the project compiling in the `View > Tool Windows > Gradle Console`.
-##### Setting Mapbox Access Token
+More information about building and distributing this project in [DISTRIBUTE.md](https://github.com/mapbox/mapbox-gl-native/blob/master/platform/android/DISTRIBUTE.md).
-_The test application (used for development purposes) uses Mapbox vector tiles, which require a Mapbox account and API access token. Obtain a free access token on the [Mapbox account page](https://www.mapbox.com/studio/account/tokens/)._
+### Additional resources
-With the first gradle invocation, gradle will take the value of the `MAPBOX_ACCESS_TOKEN` environment variable and save it to `MapboxGLAndroidSDKTestApp/src/main/res/values/developer-config.xml`. If the environement variable wasn't set, you can edit `developer-config.xml` manually and add your access token to the `mapbox_access_token` resource.
+#### Using the SDK snapshot
-#### Running project
+Instead of using the latest stable release of the Mapbox Android SDK, you can use a "snapshot" or the beta version if there is one available. Our snapshots are built every time a Github pull request adds code to this repository's `master` branch. If you'd like to use a snapshot build, your Android project's gradle file should have -SNAPSHOT appended to the SDK version number. For example `5.2.0-SNAPSHOT` or:
-Run the configuration for the `MapboxGLAndroidSDKTestApp` module and select a device or emulator to deploy on. Based on the selected device, the c++ code will be compiled for the related processor architecture. You can see the project compiling in the `View > Tool Windows > Gradle Console`.
+```java
+// Mapbox SDK dependency
+compile('com.mapbox.mapboxsdk:mapbox-android-sdk:5.2.0-SNAPSHOT@aar') {
+ transitive = true
+}
+```
+You need to have the section below in your build.gradle root folder to be able to resolve the SNAPSHOT dependencies:
+```
+allprojects {
+ repositories {
+ jcenter()
+ maven { url "http://oss.sonatype.org/content/repositories/snapshots/" }
+ }
+}
+```
-More information about building and distributing this project in [DISTRIBUTE.md][https://github.com/mapbox/mapbox-gl-native/blob/master/platform/android/DISTRIBUTE.md].
#### Symbolicating native crashes
-When hitting native crashes you can use ndk-stack to symbolicate crashes.
-More information in [this](https://github.com/mapbox/mapbox-gl-native/wiki/Getting-line-numbers-from-an-Android-crash-with-ndk-stack) guide. \ No newline at end of file
+When hitting native crashes you can use ndk-stack to symbolicate crashes.
+More information in [this](https://github.com/mapbox/mapbox-gl-native/wiki/Getting-line-numbers-from-an-Android-crash-with-ndk-stack) guide.
diff --git a/platform/android/config.cmake b/platform/android/config.cmake
index 47f894f7b9..b3888a9418 100644
--- a/platform/android/config.cmake
+++ b/platform/android/config.cmake
@@ -72,6 +72,7 @@ macro(mbgl_platform_core)
target_include_directories(mbgl-core
PUBLIC platform/default
+ PRIVATE platform/android
)
target_add_mason_package(mbgl-core PUBLIC nunicode)
@@ -146,7 +147,6 @@ add_library(mbgl-android STATIC
# Style conversion Java -> C++
platform/android/src/style/android_conversion.hpp
- platform/android/src/style/conversion/geojson.hpp
platform/android/src/style/value.cpp
platform/android/src/style/value.hpp
platform/android/src/style/conversion/url_or_tileset.hpp
@@ -353,6 +353,10 @@ macro(mbgl_platform_test)
platform/linux/src/headless_display_egl.cpp
)
+ target_include_directories(mbgl-test
+ PRIVATE platform/android
+ )
+
target_compile_options(mbgl-test
PRIVATE -fvisibility=hidden
)
diff --git a/platform/android/mbgl/gl/gl_impl.hpp b/platform/android/mbgl/gl/gl_impl.hpp
new file mode 100644
index 0000000000..b9b5d8e315
--- /dev/null
+++ b/platform/android/mbgl/gl/gl_impl.hpp
@@ -0,0 +1,5 @@
+#pragma once
+
+#define GL_GLEXT_PROTOTYPES
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
diff --git a/platform/android/src/map_renderer.cpp b/platform/android/src/map_renderer.cpp
index 7655455210..36e8142bfa 100644
--- a/platform/android/src/map_renderer.cpp
+++ b/platform/android/src/map_renderer.cpp
@@ -41,15 +41,19 @@ ActorRef<Renderer> MapRenderer::actor() const {
}
void MapRenderer::schedule(std::weak_ptr<Mailbox> scheduled) {
- // Create a runnable and schedule it on the gl thread
+ // Create a runnable
android::UniqueEnv _env = android::AttachEnv();
auto runnable = std::make_unique<MapRendererRunnable>(*_env, std::move(scheduled));
+ // Obtain ownership of the peer (gets transferred to the MapRenderer on the JVM for later GC)
+ auto peer = runnable->peer();
+
+ // Queue the event on the Java Peer
static auto queueEvent = javaClass.GetMethod<void(
jni::Object<MapRendererRunnable>)>(*_env, "queueEvent");
- javaPeer->Call(*_env, queueEvent, runnable->getPeer());
+ javaPeer->Call(*_env, queueEvent, *peer);
- // Release the object as it will be destroyed on GC of the Java Peer
+ // Release the c++ peer as it will be destroyed on GC of the Java Peer
runnable.release();
}
diff --git a/platform/android/src/map_renderer_runnable.cpp b/platform/android/src/map_renderer_runnable.cpp
index df8cba5e55..4dc6611c40 100644
--- a/platform/android/src/map_renderer_runnable.cpp
+++ b/platform/android/src/map_renderer_runnable.cpp
@@ -8,11 +8,13 @@ namespace android {
MapRendererRunnable::MapRendererRunnable(jni::JNIEnv& env, std::weak_ptr<Mailbox> mailbox_)
: mailbox(std::move(mailbox_)) {
- // Create the Java peer
+ // Create the Java peer and hold on to a global reference
+ // Not using a weak reference here as this might oerflow
+ // the weak reference table on some devices
jni::UniqueLocalFrame frame = jni::PushLocalFrame(env, 5);
static auto constructor = javaClass.GetConstructor<jlong>(env);
auto instance = javaClass.New(env, constructor, reinterpret_cast<jlong>(this));
- javaPeer = SeizeGenericWeakRef(env, jni::Object<MapRendererRunnable>(jni::NewWeakGlobalRef(env, instance.Get()).release()));
+ javaPeer = instance.NewGlobalRef(env);
}
MapRendererRunnable::~MapRendererRunnable() = default;
@@ -21,8 +23,8 @@ void MapRendererRunnable::run(jni::JNIEnv&) {
Mailbox::maybeReceive(mailbox);
}
-jni::Object<MapRendererRunnable> MapRendererRunnable::getPeer() {
- return *javaPeer;
+jni::UniqueObject<MapRendererRunnable> MapRendererRunnable::peer() {
+ return std::move(javaPeer);
}
// Static methods //
diff --git a/platform/android/src/map_renderer_runnable.hpp b/platform/android/src/map_renderer_runnable.hpp
index 75646a442d..46fb028d26 100644
--- a/platform/android/src/map_renderer_runnable.hpp
+++ b/platform/android/src/map_renderer_runnable.hpp
@@ -8,8 +8,6 @@
#include <jni/jni.hpp>
-#include "jni/generic_global_ref_deleter.hpp"
-
namespace mbgl {
namespace android {
@@ -39,10 +37,11 @@ public:
void run(jni::JNIEnv&);
- jni::Object<MapRendererRunnable> getPeer();
+ // Transfers ownership of the Peer object to the caller
+ jni::UniqueObject<MapRendererRunnable> peer();
private:
- GenericUniqueWeakObject<MapRendererRunnable> javaPeer;
+ jni::UniqueObject<MapRendererRunnable> javaPeer;
std::weak_ptr<Mailbox> mailbox;
};
diff --git a/platform/android/src/style/android_conversion.hpp b/platform/android/src/style/android_conversion.hpp
index 082fe411e2..510a9f8444 100644
--- a/platform/android/src/style/android_conversion.hpp
+++ b/platform/android/src/style/android_conversion.hpp
@@ -4,8 +4,9 @@
#include <mbgl/util/feature.hpp>
#include <mbgl/util/logging.hpp>
-#include <mbgl/style/conversion.hpp>
#include <mbgl/util/optional.hpp>
+#include <mbgl/style/conversion.hpp>
+#include <mbgl/style/conversion/geojson.hpp>
#include <jni/jni.hpp>
@@ -13,89 +14,105 @@ namespace mbgl {
namespace style {
namespace conversion {
-inline bool isUndefined(const mbgl::android::Value& value) {
- return value.isNull();
-}
+template <>
+class ConversionTraits<mbgl::android::Value> {
+public:
+ static bool isUndefined(const mbgl::android::Value& value) {
+ return value.isNull();
+ }
-inline bool isArray(const mbgl::android::Value& value) {
- return value.isArray();
-}
+ static bool isArray(const mbgl::android::Value& value) {
+ return value.isArray();
+ }
-inline bool isObject(const mbgl::android::Value& value) {
- return value.isObject();
-}
+ static bool isObject(const mbgl::android::Value& value) {
+ return value.isObject();
+ }
-inline std::size_t arrayLength(const mbgl::android::Value& value) {
- return value.getLength();;
-}
+ static std::size_t arrayLength(const mbgl::android::Value& value) {
+ return value.getLength();;
+ }
-inline mbgl::android::Value arrayMember(const mbgl::android::Value& value, std::size_t i) {
- return value.get(i);
-}
+ static mbgl::android::Value arrayMember(const mbgl::android::Value& value, std::size_t i) {
+ return value.get(i);
+ }
-inline optional<mbgl::android::Value> objectMember(const mbgl::android::Value& value, const char* key) {
- mbgl::android::Value member = value.get(key);
+ static optional<mbgl::android::Value> objectMember(const mbgl::android::Value& value, const char* key) {
+ mbgl::android::Value member = value.get(key);
+ if (!member.isNull()) {
+ return member;
+ } else {
+ return {};
+ }
+ }
- if (!member.isNull()) {
- return member;
- } else {
+ template <class Fn>
+ static optional<Error> eachMember(const mbgl::android::Value&, Fn&&) {
+ // TODO
+ mbgl::Log::Warning(mbgl::Event::Android, "eachMember not implemented");
return {};
}
-}
-template <class Fn>
-optional<Error> eachMember(const mbgl::android::Value&, Fn&&) {
- // TODO
- mbgl::Log::Warning(mbgl::Event::Android, "eachMember not implemented");
- return {};
-}
+ static optional<bool> toBool(const mbgl::android::Value& value) {
+ if (value.isBool()) {
+ return value.toBool();
+ } else {
+ return {};
+ }
+ }
-inline optional<bool> toBool(const mbgl::android::Value& value) {
- if (value.isBool()) {
- return value.toBool();
- } else {
- return {};
+ static optional<float> toNumber(const mbgl::android::Value& value) {
+ if (value.isNumber()) {
+ auto num = value.toFloat();
+ return num;
+ } else {
+ return {};
+ }
}
-}
-inline optional<float> toNumber(const mbgl::android::Value& value) {
- if (value.isNumber()) {
- auto num = value.toFloat();
- return num;
- } else {
- return {};
+ static optional<double> toDouble(const mbgl::android::Value& value) {
+ if (value.isNumber()) {
+ return value.toDouble();
+ } else {
+ return {};
+ }
}
-}
-inline optional<double> toDouble(const mbgl::android::Value& value) {
- if (value.isNumber()) {
- return value.toDouble();
- } else {
- return {};
+ static optional<std::string> toString(const mbgl::android::Value& value) {
+ if (value.isString()) {
+ return value.toString();
+ } else {
+ return {};
+ }
}
-}
-inline optional<std::string> toString(const mbgl::android::Value& value) {
- if (value.isString()) {
- return value.toString();
- } else {
- return {};
+ static optional<Value> toValue(const mbgl::android::Value& value) {
+ if (value.isNull()) {
+ return {};
+ } else if (value.isBool()) {
+ return { value.toBool() };
+ } else if (value.isString()) {
+ return { value.toString() };
+ } else if (value.isNumber()) {
+ auto doubleVal = value.toDouble();
+ return { doubleVal - (int) doubleVal > 0.0 ? doubleVal : value.toLong() };
+ } else {
+ return {};
+ }
}
-}
-inline optional<Value> toValue(const mbgl::android::Value& value) {
- if (value.isNull()) {
- return {};
- } else if (value.isBool()) {
- return { value.toBool() };
- } else if (value.isString()) {
- return { value.toString() };
- } else if (value.isNumber()) {
- auto doubleVal = value.toDouble();
- return { doubleVal - (int) doubleVal > 0.0 ? doubleVal : value.toLong() };
- } else {
- return {};
+ static optional<GeoJSON> toGeoJSON(const mbgl::android::Value &value, Error &error) {
+ if (value.isNull() || !value.isString()) {
+ error = { "no json data found" };
+ return {};
+ }
+ return parseGeoJSON(value.toString(), error);
}
+};
+
+template <class T, class...Args>
+optional<T> convert(mbgl::android::Value&& value, Error& error, Args&&...args) {
+ return convert<T>(Convertible(std::move(value)), error, std::forward<Args>(args)...);
}
} // namespace conversion
diff --git a/platform/android/src/style/conversion/filter.hpp b/platform/android/src/style/conversion/filter.hpp
index 1f0abcf3a4..c154e88e7c 100644
--- a/platform/android/src/style/conversion/filter.hpp
+++ b/platform/android/src/style/conversion/filter.hpp
@@ -16,9 +16,8 @@ namespace conversion {
inline optional<mbgl::style::Filter> toFilter(jni::JNIEnv& env, jni::Array<jni::Object<>> jfilter) {
mbgl::optional<mbgl::style::Filter> filter;
if (jfilter) {
- Value filterValue(env, jfilter);
mbgl::style::conversion::Error error;
- auto converted = mbgl::style::conversion::convert<mbgl::style::Filter>(filterValue, error);
+ auto converted = mbgl::style::conversion::convert<mbgl::style::Filter>(Value(env, jfilter), error);
if (!converted) {
mbgl::Log::Error(mbgl::Event::JNI, "Error converting filter: " + error.message);
}
diff --git a/platform/android/src/style/conversion/geojson.hpp b/platform/android/src/style/conversion/geojson.hpp
deleted file mode 100644
index 748fe7361e..0000000000
--- a/platform/android/src/style/conversion/geojson.hpp
+++ /dev/null
@@ -1,24 +0,0 @@
-#pragma once
-
-#include <mapbox/geojson.hpp>
-#include <mbgl/style/conversion.hpp>
-#include <mbgl/style/conversion/geojson.hpp>
-#include <jni/jni.hpp>
-
-namespace mbgl {
-namespace style {
-namespace conversion {
-
-template <>
-optional<GeoJSON> Converter<GeoJSON>::operator()(const mbgl::android::Value& value, Error& error) const {
- if(value.isNull() || !value.isString()) {
- error = { "no json data found" };
- return {};
- }
-
- return convert<GeoJSON>(value.toString(), error);
-}
-
-} // namespace conversion
-} // namespace style
-} // namespace mbgl
diff --git a/platform/android/src/style/conversion/url_or_tileset.hpp b/platform/android/src/style/conversion/url_or_tileset.hpp
index 00ef913d41..92c1182a63 100644
--- a/platform/android/src/style/conversion/url_or_tileset.hpp
+++ b/platform/android/src/style/conversion/url_or_tileset.hpp
@@ -17,18 +17,19 @@ namespace android {
// This conversion is expected not to fail because it's used only in contexts where
// the value was originally a String or TileSet object on the Java side. If it fails
// to convert, it's a bug in our serialization or Java-side static typing.
-inline variant<std::string, Tileset> convertURLOrTileset(const Value& value) {
+inline variant<std::string, Tileset> convertURLOrTileset(mbgl::android::Value&& value) {
using namespace mbgl::style::conversion;
- if (isObject(value)) {
+ const Convertible convertible(std::move(value));
+ if (isObject(convertible)) {
Error error;
- optional<Tileset> tileset = convert<Tileset>(value, error);
+ optional<Tileset> tileset = convert<Tileset>(convertible, error);
if (!tileset) {
throw std::logic_error(error.message);
}
return { *tileset };
} else {
- return { *toString(value) };
+ return { *toString(convertible) };
}
}
diff --git a/platform/android/src/style/layers/layer.cpp b/platform/android/src/style/layers/layer.cpp
index 02a1f0be82..31032b117f 100644
--- a/platform/android/src/style/layers/layer.cpp
+++ b/platform/android/src/style/layers/layer.cpp
@@ -4,11 +4,20 @@
#include <jni/jni.hpp>
#include <mbgl/style/style.hpp>
+#include <mbgl/style/filter.hpp>
#include <mbgl/style/transition_options.hpp>
+#include <mbgl/style/layers/background_layer.hpp>
+#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/line_layer.hpp>
+#include <mbgl/style/layers/raster_layer.hpp>
+#include <mbgl/style/layers/symbol_layer.hpp>
#include <mbgl/util/logging.hpp>
// Java -> C++ conversion
#include <mbgl/style/conversion.hpp>
+#include <mbgl/style/conversion/filter.hpp>
#include <mbgl/style/conversion/layer.hpp>
#include <mbgl/style/conversion/source.hpp>
@@ -78,10 +87,8 @@ namespace android {
}
void Layer::setLayoutProperty(jni::JNIEnv& env, jni::String jname, jni::Object<> jvalue) {
- Value value(env, jvalue);
-
// Convert and set property
- optional<mbgl::style::conversion::Error> error = mbgl::style::conversion::setLayoutProperty(layer, jni::Make<std::string>(env, jname), value);
+ optional<mbgl::style::conversion::Error> error = mbgl::style::conversion::setLayoutProperty(layer, jni::Make<std::string>(env, jname), Value(env, jvalue));
if (error) {
mbgl::Log::Error(mbgl::Event::JNI, "Error setting property: " + jni::Make<std::string>(env, jname) + " " + error->message);
return;
@@ -89,10 +96,8 @@ namespace android {
}
void Layer::setPaintProperty(jni::JNIEnv& env, jni::String jname, jni::Object<> jvalue) {
- Value value(env, jvalue);
-
// Convert and set property
- optional<mbgl::style::conversion::Error> error = mbgl::style::conversion::setPaintProperty(layer, jni::Make<std::string>(env, jname), value);
+ optional<mbgl::style::conversion::Error> error = mbgl::style::conversion::setPaintProperty(layer, jni::Make<std::string>(env, jname), Value(env, jvalue));
if (error) {
mbgl::Log::Error(mbgl::Event::JNI, "Error setting property: " + jni::Make<std::string>(env, jname) + " " + error->message);
return;
@@ -116,10 +121,8 @@ namespace android {
using namespace mbgl::style;
using namespace mbgl::style::conversion;
- Value wrapped(env, jfilter);
-
Error error;
- optional<Filter> converted = convert<Filter>(wrapped, error);
+ optional<Filter> converted = convert<Filter>(Value(env, jfilter), error);
if (!converted) {
mbgl::Log::Error(mbgl::Event::JNI, "Error setting filter: " + error.message);
return;
diff --git a/platform/android/src/style/sources/geojson_source.cpp b/platform/android/src/style/sources/geojson_source.cpp
index 90ef851eba..4468b453f3 100644
--- a/platform/android/src/style/sources/geojson_source.cpp
+++ b/platform/android/src/style/sources/geojson_source.cpp
@@ -5,15 +5,15 @@
// Java -> C++ conversion
#include "../android_conversion.hpp"
#include "../conversion/filter.hpp"
-#include "../conversion/geojson.hpp"
+#include <mbgl/style/conversion.hpp>
+#include <mbgl/style/conversion/geojson.hpp>
+#include <mbgl/style/conversion/geojson_options.hpp>
// C++ -> Java conversion
#include "../../conversion/conversion.hpp"
#include "../../conversion/collection.hpp"
#include "../../geojson/conversion/feature.hpp"
#include "../conversion/url_or_tileset.hpp"
-#include <mbgl/style/conversion.hpp>
-#include <mbgl/style/conversion/geojson_options.hpp>
#include <string>
@@ -29,7 +29,7 @@ namespace android {
return style::GeoJSONOptions();
}
Error error;
- optional<style::GeoJSONOptions> result = convert<style::GeoJSONOptions>(Value(env, options), error);
+ optional<style::GeoJSONOptions> result = convert<style::GeoJSONOptions>(mbgl::android::Value(env, options), error);
if (!result) {
throw std::logic_error(error.message);
}
@@ -54,7 +54,7 @@ namespace android {
// Convert the jni object
Error error;
- optional<GeoJSON> converted = convert<GeoJSON>(Value(env, json), error);
+ optional<GeoJSON> converted = convert<GeoJSON>(mbgl::android::Value(env, json), error);
if(!converted) {
mbgl::Log::Error(mbgl::Event::JNI, "Error setting geo json: " + error.message);
return;
diff --git a/platform/android/src/style/value.cpp b/platform/android/src/style/value.cpp
index e1cd81d7fd..70bdea6677 100644
--- a/platform/android/src/style/value.cpp
+++ b/platform/android/src/style/value.cpp
@@ -24,8 +24,6 @@ namespace android {
Value::Value(jni::JNIEnv& _env, jni::jobject* _value) : env(_env), value(_value, ObjectDeleter(env)) {}
- Value::~Value() = default;
-
bool Value::isNull() const {
return value == nullptr;
}
diff --git a/platform/android/src/style/value.hpp b/platform/android/src/style/value.hpp
index 7464bae832..2057b93454 100644
--- a/platform/android/src/style/value.hpp
+++ b/platform/android/src/style/value.hpp
@@ -9,9 +9,13 @@ namespace android {
class Value {
public:
-
Value(jni::JNIEnv&, jni::jobject*);
- virtual ~Value();
+
+ Value(Value&&) = default;
+ Value& operator=(Value&&) = default;
+
+ Value(const Value&) = delete;
+ Value& operator=(const Value&) = delete;
bool isNull() const;
bool isArray() const;
diff --git a/platform/darwin/mbgl/gl/gl_impl.hpp b/platform/darwin/mbgl/gl/gl_impl.hpp
new file mode 100644
index 0000000000..b4c062a474
--- /dev/null
+++ b/platform/darwin/mbgl/gl/gl_impl.hpp
@@ -0,0 +1,16 @@
+#pragma once
+
+#include "TargetConditionals.h"
+#if TARGET_OS_IPHONE
+ #include <OpenGLES/ES2/gl.h>
+ #include <OpenGLES/ES2/glext.h>
+#elif TARGET_IPHONE_SIMULATOR
+ #include <OpenGLES/ES2/gl.h>
+ #include <OpenGLES/ES2/glext.h>
+#elif TARGET_OS_MAC
+ #include <OpenGL/OpenGL.h>
+ #include <OpenGL/gl.h>
+ #include <OpenGL/glext.h>
+#else
+ #error Unsupported Apple platform
+#endif
diff --git a/platform/darwin/src/MGLConversion.h b/platform/darwin/src/MGLConversion.h
index d6363b28eb..0d18d4e716 100644
--- a/platform/darwin/src/MGLConversion.h
+++ b/platform/darwin/src/MGLConversion.h
@@ -1,9 +1,4 @@
-#import <Foundation/Foundation.h>
-
-#include <mbgl/util/logging.hpp>
#include <mbgl/style/conversion.hpp>
-#include <mbgl/util/feature.hpp>
-#include <mbgl/util/optional.hpp>
NS_ASSUME_NONNULL_BEGIN
@@ -11,128 +6,147 @@ namespace mbgl {
namespace style {
namespace conversion {
-/**
- A minimal wrapper class conforming to the requirements for `objectMember(v, name)` (see mbgl/style/conversion.hpp)
- This is necessary because using `NSObject*` as the value type in `optional<NSObject*>` causes problems for the ARC,
- due to things like `optional(const value_type& __v)`
- */
-class OptionalNSObjectValue {
+// A wrapper class for `id`, so as not to confuse ARC.
+class Holder {
public:
- OptionalNSObjectValue(NSObject * _Nullable _value) : value(_value) {}
-
- explicit operator bool() const {
- return value;
+ Holder(const id v) : value(v) {}
+ const id value;
+};
+
+template <>
+class ConversionTraits<Holder> {
+public:
+ static bool isUndefined(const Holder& holder) {
+ const id value = holder.value;
+ return !value || value == [NSNull null];
}
-
- NSObject * _Nullable operator*() {
- NSCAssert(this, @"Expected non-null value.");
- return value;
+
+ static bool isArray(const Holder& holder) {
+ const id value = holder.value;
+ return [value isKindOfClass:[NSArray class]];
}
-private:
- NSObject * _Nullable value;
-};
-inline bool isUndefined(const id value) {
- return !value || value == [NSNull null];
-}
+ static bool isObject(const Holder& holder) {
+ const id value = holder.value;
+ return [value isKindOfClass:[NSDictionary class]];
+ }
-inline bool isArray(const id value) {
- return [value isKindOfClass:[NSArray class]];
-}
+ static std::size_t arrayLength(const Holder& holder) {
+ const id value = holder.value;
+ NSCAssert([value isKindOfClass:[NSArray class]], @"Value must be an NSArray for getLength().");
+ NSArray *array = value;
+ auto length = [array count];
+ NSCAssert(length <= std::numeric_limits<size_t>::max(), @"Array length out of bounds.");
+ return length;
+ }
-inline bool isObject(const id value) {
- return [value isKindOfClass:[NSDictionary class]];
-}
+ static Holder arrayMember(const Holder& holder, std::size_t i) {
+ const id value = holder.value;
+ NSCAssert([value isKindOfClass:[NSArray class]], @"Value must be an NSArray for get(int).");
+ NSCAssert(i < NSUIntegerMax, @"Index must be less than NSUIntegerMax");
+ return {[value objectAtIndex: i]};
+ }
-inline std::size_t arrayLength(const id value) {
- NSCAssert([value isKindOfClass:[NSArray class]], @"Value must be an NSArray for getLength().");
- NSArray *array = value;
- auto length = [array count];
- NSCAssert(length <= std::numeric_limits<size_t>::max(), @"Array length out of bounds.");
- return length;
-}
+ static optional<Holder> objectMember(const Holder& holder, const char *key) {
+ const id value = holder.value;
+ NSCAssert([value isKindOfClass:[NSDictionary class]], @"Value must be an NSDictionary for get(string).");
+ NSObject *member = [value objectForKey: @(key)];
+ if (member && member != [NSNull null]) {
+ return {member};
+ } else {
+ return {};
+ }
+ }
-inline NSObject *arrayMember(const id value, std::size_t i) {
- NSCAssert([value isKindOfClass:[NSArray class]], @"Value must be an NSArray for get(int).");
- NSCAssert(i < NSUIntegerMax, @"Index must be less than NSUIntegerMax");
- return [value objectAtIndex: i];
-}
+// Compiler is wrong about `Fn` parameter missing a nullability specifier.
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wnullability-completeness"
+ template <class Fn>
+ static optional<Error> eachMember(const Holder&, Fn&&) {
+#pragma clang diagnostic pop
+ // Not implemented (unneeded for MGLStyleFunction conversion).
+ NSCAssert(NO, @"eachMember not implemented");
+ return {};
+ }
-inline OptionalNSObjectValue objectMember(const id value, const char *key) {
- NSCAssert([value isKindOfClass:[NSDictionary class]], @"Value must be an NSDictionary for get(string).");
- NSObject *member = [value objectForKey: @(key)];
- if (member && member != [NSNull null]) {
- return { member };
- } else {
- return { nullptr };
+ static optional<bool> toBool(const Holder& holder) {
+ const id value = holder.value;
+ if (_isBool(value)) {
+ return ((NSNumber *)value).boolValue;
+ } else {
+ return {};
+ }
}
-}
-// Not implemented (unneeded for MGLStyleFunction conversion):
-// optional<Error> eachMember(const NSObject*, Fn&&)
+ static optional<float> toNumber(const Holder& holder) {
+ const id value = holder.value;
+ if (_isNumber(value)) {
+ return ((NSNumber *)value).floatValue;
+ } else {
+ return {};
+ }
+ }
-inline bool _isBool(const id value) {
- if (![value isKindOfClass:[NSNumber class]]) return false;
- // char: 32-bit boolean
- // BOOL: 64-bit boolean
- NSNumber *number = value;
- return ((strcmp([number objCType], @encode(char)) == 0) ||
- (strcmp([number objCType], @encode(BOOL)) == 0));
-}
-
-inline bool _isNumber(const id value) {
- return [value isKindOfClass:[NSNumber class]] && !_isBool(value);
-}
-
-inline bool _isString(const id value) {
- return [value isKindOfClass:[NSString class]];
-}
+ static optional<double> toDouble(const Holder& holder) {
+ const id value = holder.value;
+ if (_isNumber(value)) {
+ return ((NSNumber *)value).doubleValue;
+ } else {
+ return {};
+ }
+ }
-inline optional<bool> toBool(const id value) {
- if (_isBool(value)) {
- return ((NSNumber *)value).boolValue;
- } else {
- return {};
+ static optional<std::string> toString(const Holder& holder) {
+ const id value = holder.value;
+ if (_isString(value)) {
+ return std::string(static_cast<const char *>([value UTF8String]));
+ } else {
+ return {};
+ }
}
-}
-inline optional<float> toNumber(const id value) {
- if (_isNumber(value)) {
- return ((NSNumber *)value).floatValue;
- } else {
- return {};
+ static optional<mbgl::Value> toValue(const Holder& holder) {
+ const id value = holder.value;
+ if (isUndefined(value)) {
+ return {};
+ } else if (_isBool(value)) {
+ return { *toBool(holder) };
+ } else if ( _isString(value)) {
+ return { *toString(holder) };
+ } else if (_isNumber(value)) {
+ // Need to cast to a double here as the float is otherwise considered a bool...
+ return { static_cast<double>(*toNumber(holder)) };
+ } else {
+ return {};
+ }
}
-}
-inline optional<double> toDouble(const id value) {
- if (_isNumber(value)) {
- return ((NSNumber *)value).doubleValue;
- } else {
+ static optional<GeoJSON> toGeoJSON(const Holder& holder, Error& error) {
+ error = { "toGeoJSON not implemented" };
return {};
}
-}
-inline optional<std::string> toString(const id value) {
- if (_isString(value)) {
- return std::string(static_cast<const char *>([value UTF8String]));
- } else {
- return {};
+private:
+ static bool _isBool(const id value) {
+ if (![value isKindOfClass:[NSNumber class]]) return false;
+ // char: 32-bit boolean
+ // BOOL: 64-bit boolean
+ NSNumber *number = value;
+ return ((strcmp([number objCType], @encode(char)) == 0) ||
+ (strcmp([number objCType], @encode(BOOL)) == 0));
}
-}
-inline optional<mbgl::Value> toValue(const id value) {
- if (isUndefined(value)) {
- return {};
- } else if (_isBool(value)) {
- return { *toBool(value) };
- } else if ( _isString(value)) {
- return { *toString(value) };
- } else if (_isNumber(value)) {
- // Need to cast to a double here as the float is otherwise considered a bool...
- return { static_cast<double>(*toNumber(value)) };
- } else {
- return {};
+ static bool _isNumber(const id value) {
+ return [value isKindOfClass:[NSNumber class]] && !_isBool(value);
}
+
+ static bool _isString(const id value) {
+ return [value isKindOfClass:[NSString class]];
+ }
+};
+
+inline Convertible makeConvertible(const id value) {
+ return Convertible(Holder(value));
}
} // namespace conversion
@@ -140,4 +154,3 @@ inline optional<mbgl::Value> toValue(const id value) {
} // namespace mbgl
NS_ASSUME_NONNULL_END
-
diff --git a/platform/darwin/src/MGLStyleValue_Private.h b/platform/darwin/src/MGLStyleValue_Private.h
index 2155c657bd..5914e0a2aa 100644
--- a/platform/darwin/src/MGLStyleValue_Private.h
+++ b/platform/darwin/src/MGLStyleValue_Private.h
@@ -124,9 +124,9 @@ public:
if ([value isKindOfClass:[MGLConstantStyleValue class]]) {
return toMBGLConstantValue((MGLConstantStyleValue<ObjCType> *)value);
} else if ([value isKindOfClass:[MGLStyleFunction class]]) {
- auto rawValue = toRawStyleSpecValue((MGLStyleFunction<ObjCType> *) value);
mbgl::style::conversion::Error error;
- auto result = mbgl::style::conversion::convert<mbgl::style::DataDrivenPropertyValue<MBGLType>>(rawValue, error);
+ auto result = mbgl::style::conversion::convert<mbgl::style::DataDrivenPropertyValue<MBGLType>>(
+ mbgl::style::conversion::makeConvertible(toRawStyleSpecValue((MGLStyleFunction<ObjCType> *) value)), error);
NSCAssert(result, @(error.message.c_str()));
return *result;
} else {
diff --git a/platform/default/mbgl/gl/headless_frontend.cpp b/platform/default/mbgl/gl/headless_frontend.cpp
index 2cbb624bd0..9df35657b0 100644
--- a/platform/default/mbgl/gl/headless_frontend.cpp
+++ b/platform/default/mbgl/gl/headless_frontend.cpp
@@ -7,11 +7,11 @@
namespace mbgl {
-HeadlessFrontend::HeadlessFrontend(float pixelRatio_, FileSource& fileSource, Scheduler& scheduler, const optional<std::string> programCacheDir)
- : HeadlessFrontend({ 256, 256 }, pixelRatio_, fileSource, scheduler, programCacheDir) {
+HeadlessFrontend::HeadlessFrontend(float pixelRatio_, FileSource& fileSource, Scheduler& scheduler, const optional<std::string> programCacheDir, GLContextMode mode)
+ : HeadlessFrontend({ 256, 256 }, pixelRatio_, fileSource, scheduler, programCacheDir, mode) {
}
-HeadlessFrontend::HeadlessFrontend(Size size_, float pixelRatio_, FileSource& fileSource, Scheduler& scheduler, const optional<std::string> programCacheDir)
+HeadlessFrontend::HeadlessFrontend(Size size_, float pixelRatio_, FileSource& fileSource, Scheduler& scheduler, const optional<std::string> programCacheDir, GLContextMode mode)
: size(size_),
pixelRatio(pixelRatio_),
backend({ static_cast<uint32_t>(size.width * pixelRatio),
@@ -22,7 +22,7 @@ HeadlessFrontend::HeadlessFrontend(Size size_, float pixelRatio_, FileSource& fi
renderer->render(*updateParameters);
}
}),
- renderer(std::make_unique<Renderer>(backend, pixelRatio, fileSource, scheduler, GLContextMode::Unique, programCacheDir)) {
+ renderer(std::make_unique<Renderer>(backend, pixelRatio, fileSource, scheduler, mode, programCacheDir)) {
}
HeadlessFrontend::~HeadlessFrontend() = default;
diff --git a/platform/default/mbgl/gl/headless_frontend.hpp b/platform/default/mbgl/gl/headless_frontend.hpp
index 4d1116904e..0530d84a25 100644
--- a/platform/default/mbgl/gl/headless_frontend.hpp
+++ b/platform/default/mbgl/gl/headless_frontend.hpp
@@ -1,5 +1,6 @@
#pragma once
+#include <mbgl/renderer/mode.hpp>
#include <mbgl/renderer/renderer_frontend.hpp>
#include <mbgl/gl/headless_backend.hpp>
#include <mbgl/util/async_task.hpp>
@@ -18,8 +19,8 @@ class TransformState;
class HeadlessFrontend : public RendererFrontend {
public:
- HeadlessFrontend(float pixelRatio_, FileSource&, Scheduler&, const optional<std::string> programCacheDir = {});
- HeadlessFrontend(Size, float pixelRatio_, FileSource&, Scheduler&, const optional<std::string> programCacheDir = {});
+ HeadlessFrontend(float pixelRatio_, FileSource&, Scheduler&, const optional<std::string> programCacheDir = {}, GLContextMode mode = GLContextMode::Unique);
+ HeadlessFrontend(Size, float pixelRatio_, FileSource&, Scheduler&, const optional<std::string> programCacheDir = {}, GLContextMode mode = GLContextMode::Unique);
~HeadlessFrontend() override;
void reset() override;
diff --git a/platform/default/mbgl/storage/offline.cpp b/platform/default/mbgl/storage/offline.cpp
index 9ec789f725..7670790be9 100644
--- a/platform/default/mbgl/storage/offline.cpp
+++ b/platform/default/mbgl/storage/offline.cpp
@@ -24,7 +24,7 @@ OfflineTilePyramidRegionDefinition::OfflineTilePyramidRegionDefinition(
}
}
-std::vector<CanonicalTileID> OfflineTilePyramidRegionDefinition::tileCover(SourceType type, uint16_t tileSize, const Range<uint8_t>& zoomRange) const {
+std::vector<CanonicalTileID> OfflineTilePyramidRegionDefinition::tileCover(style::SourceType type, uint16_t tileSize, const Range<uint8_t>& zoomRange) const {
const Range<uint8_t> clampedZoomRange = coveringZoomRange(type, tileSize, zoomRange);
std::vector<CanonicalTileID> result;
@@ -38,7 +38,7 @@ std::vector<CanonicalTileID> OfflineTilePyramidRegionDefinition::tileCover(Sourc
return result;
}
-uint64_t OfflineTilePyramidRegionDefinition::tileCount(SourceType type, uint16_t tileSize, const Range<uint8_t>& zoomRange) const {
+uint64_t OfflineTilePyramidRegionDefinition::tileCount(style::SourceType type, uint16_t tileSize, const Range<uint8_t>& zoomRange) const {
const Range<uint8_t> clampedZoomRange = coveringZoomRange(type, tileSize, zoomRange);
unsigned long result = 0;;
@@ -49,7 +49,7 @@ uint64_t OfflineTilePyramidRegionDefinition::tileCount(SourceType type, uint16_t
return result;
}
-Range<uint8_t> OfflineTilePyramidRegionDefinition::coveringZoomRange(SourceType type, uint16_t tileSize, const Range<uint8_t>& zoomRange) const {
+Range<uint8_t> OfflineTilePyramidRegionDefinition::coveringZoomRange(style::SourceType type, uint16_t tileSize, const Range<uint8_t>& zoomRange) const {
double minZ = std::max<double>(util::coveringZoomLevel(minZoom, type, tileSize), zoomRange.min);
double maxZ = std::min<double>(util::coveringZoomLevel(maxZoom, type, tileSize), zoomRange.max);
diff --git a/platform/default/mbgl/storage/offline_download.hpp b/platform/default/mbgl/storage/offline_download.hpp
index c978ded931..437f221c11 100644
--- a/platform/default/mbgl/storage/offline_download.hpp
+++ b/platform/default/mbgl/storage/offline_download.hpp
@@ -60,7 +60,7 @@ private:
std::deque<Resource> resourcesRemaining;
void queueResource(Resource);
- void queueTiles(SourceType, uint16_t tileSize, const Tileset&);
+ void queueTiles(style::SourceType, uint16_t tileSize, const Tileset&);
};
} // namespace mbgl
diff --git a/platform/glfw/settings_json.hpp b/platform/glfw/settings_json.hpp
index 49ea00e3e1..c89accb8af 100644
--- a/platform/glfw/settings_json.hpp
+++ b/platform/glfw/settings_json.hpp
@@ -17,6 +17,9 @@ public:
double zoom = 0;
double bearing = 0;
double pitch = 0;
+ bool axonometric = false;
+ double xSkew = 0.0;
+ double ySkew = 1.0;
EnumType debug = 0;
bool online = true;
diff --git a/platform/ios/CHANGELOG.md b/platform/ios/CHANGELOG.md
index d449379ea6..404b4c7e2d 100644
--- a/platform/ios/CHANGELOG.md
+++ b/platform/ios/CHANGELOG.md
@@ -2,6 +2,12 @@
Mapbox welcomes participation and contributions from everyone. Please read [CONTRIBUTING.md](../../CONTRIBUTING.md) to get started.
+## master
+
+### Annotations and user interaction
+
+* Increased the default maximum zoom level from 20 to 22. ([#9835](https://github.com/mapbox/mapbox-gl-native/pull/9835))
+
## 3.7.0
### Networking and storage
diff --git a/platform/ios/INSTALL.md b/platform/ios/INSTALL.md
index b64d9c650f..e8c821ecee 100644
--- a/platform/ios/INSTALL.md
+++ b/platform/ios/INSTALL.md
@@ -1,6 +1,6 @@
# Integrating custom builds of the Mapbox iOS SDK into your application
-This document explains how to build a development version of Mapbox iOS SDK for use in your own Cocoa Touch application. To use a production-ready version of the SDK, see the [Mapbox iOS SDK homepage](https://mapbox.com/ios-sdk).
+This document explains how to build a development version of Mapbox iOS SDK for use in your own Cocoa Touch application. To use a production-ready version of the SDK, see the [Mapbox iOS SDK installation page](https://www.mapbox.com/install/ios/).
### Requirements
@@ -113,7 +113,7 @@ If using the static framework, add `$(inherited)` to your target’s Other Linke
#### Carthage
-For instructions on installing stable release versions of the Mapbox iOS SDK with Carthage, see [our website](https://www.mapbox.com/ios-sdk/). If you require a build without symbols pre-stripped, use [this feed URL](https://www.mapbox.com/ios-sdk/Mapbox-iOS-SDK-symbols.json) with Carthage.
+For instructions on installing stable release versions of the Mapbox iOS SDK with Carthage, see [our website](https://www.mapbox.com/install/ios/carthage/). If you require a build without symbols pre-stripped, use [this feed URL](https://www.mapbox.com/ios-sdk/Mapbox-iOS-SDK-symbols.json) with Carthage.
##### Testing pre-releases with Carthage
diff --git a/platform/ios/README.md b/platform/ios/README.md
index af01aed18d..de70c4e891 100644
--- a/platform/ios/README.md
+++ b/platform/ios/README.md
@@ -4,9 +4,10 @@
A library based on [Mapbox GL Native](../../README.md) for embedding interactive map views with scalable, customizable vector maps into Cocoa Touch applications on iOS 8.0 and above using Objective-C, Swift, or Interface Builder.
-This repository is for day-to-day development of the SDK. Building the SDK yourself requires [a number of dependencies and steps](../../INSTALL.md) that are unnecessary for developing production applications. For production applications, please consider installing an official, prebuilt release instead; see the [Mapbox iOS SDK website](https://www.mapbox.com/ios-sdk/) for installation instructions.
+This repository is for day-to-day development of the SDK. Building the SDK yourself requires [a number of dependencies and steps](../../INSTALL.md) that are unnecessary for developing production applications. For production applications, please consider installing an official, production-ready version instead.
-* [Integrating the Mapbox iOS SDK into your application](INSTALL.md)
-* [Contributing to the Mapbox iOS SDK](DEVELOPING.md)
+* [Integrate the Mapbox iOS SDK into your application](https://www.mapbox.com/install/ios/).
+* [Learn about custom builds](INSTALL.md)
+* [Contribute to the Mapbox iOS SDK](DEVELOPING.md)
![](docs/img/screenshot.png)
diff --git a/platform/ios/src/MGLMapView.mm b/platform/ios/src/MGLMapView.mm
index c960c60c78..4e9ee9a4c7 100644
--- a/platform/ios/src/MGLMapView.mm
+++ b/platform/ios/src/MGLMapView.mm
@@ -18,6 +18,7 @@
#include <mbgl/style/image.hpp>
#include <mbgl/style/transition_options.hpp>
#include <mbgl/style/layers/custom_layer.hpp>
+#include <mbgl/renderer/mode.hpp>
#include <mbgl/renderer/renderer.hpp>
#include <mbgl/renderer/renderer_backend.hpp>
#include <mbgl/renderer/backend_scope.hpp>
diff --git a/platform/linux/config.cmake b/platform/linux/config.cmake
index 47c4c68806..edac8e59ac 100644
--- a/platform/linux/config.cmake
+++ b/platform/linux/config.cmake
@@ -76,6 +76,7 @@ macro(mbgl_platform_core)
target_include_directories(mbgl-core
PRIVATE platform/default
+ PRIVATE platform/linux
)
target_add_mason_package(mbgl-core PUBLIC nunicode)
@@ -149,6 +150,10 @@ macro(mbgl_platform_test)
PRIVATE platform/default/mbgl/test/main.cpp
)
+ target_include_directories(mbgl-test
+ PRIVATE platform/linux
+ )
+
set_source_files_properties(
platform/default/mbgl/test/main.cpp
PROPERTIES
diff --git a/platform/linux/mbgl/gl/gl_impl.hpp b/platform/linux/mbgl/gl/gl_impl.hpp
new file mode 100644
index 0000000000..ae829f3f7e
--- /dev/null
+++ b/platform/linux/mbgl/gl/gl_impl.hpp
@@ -0,0 +1,11 @@
+#pragma once
+
+#if MBGL_USE_GLES2
+ #define GL_GLEXT_PROTOTYPES
+ #include <GLES2/gl2.h>
+ #include <GLES2/gl2ext.h>
+#else
+ #define GL_GLEXT_PROTOTYPES
+ #include <GL/gl.h>
+ #include <GL/glext.h>
+#endif
diff --git a/platform/macos/CHANGELOG.md b/platform/macos/CHANGELOG.md
index 418b3b2006..cad254e670 100644
--- a/platform/macos/CHANGELOG.md
+++ b/platform/macos/CHANGELOG.md
@@ -27,8 +27,11 @@
* Increased the default maximum zoom level from 20 to 22. ([#9835](https://github.com/mapbox/mapbox-gl-native/pull/9835))
* Added an `overlays` property to `MGLMapView`. ([#8617](https://github.com/mapbox/mapbox-gl-native/pull/8617))
* Added `-[MGLMapView cameraThatFitsShape:direction:edgePadding:]` to get a camera with zoom level and center coordinate computed to fit a shape. ([#10107](https://github.com/mapbox/mapbox-gl-native/pull/10107))
+<<<<<<< HEAD
+=======
* Added support selection of shape and polyline annotations.([#9984](https://github.com/mapbox/mapbox-gl-native/pull/9984))
* Fixed an issue where a shape annotation callout was not displayed if the centroid was not visible. ([#10255](https://github.com/mapbox/mapbox-gl-native/pull/10255))
+>>>>>>> release-agua
### Other changes
diff --git a/platform/macos/config.cmake b/platform/macos/config.cmake
index aca99f9b40..2716f7c84d 100644
--- a/platform/macos/config.cmake
+++ b/platform/macos/config.cmake
@@ -133,6 +133,10 @@ macro(mbgl_platform_test)
PRIVATE platform/default/mbgl/test/main.cpp
)
+ target_include_directories(mbgl-test
+ PRIVATE platform/macos
+ )
+
set_source_files_properties(
platform/default/mbgl/test/main.cpp
PROPERTIES
diff --git a/platform/node/CHANGELOG.md b/platform/node/CHANGELOG.md
index 1d166a9a79..4b93fca25d 100644
--- a/platform/node/CHANGELOG.md
+++ b/platform/node/CHANGELOG.md
@@ -1,3 +1,11 @@
+# 3.5.8 - October 19, 2017
+- Fixes an issue that causes memory leaks when not deleting the frontend object
+ in NodeMap::release()
+- Fixes a crash in Earcut: [#10245](https://github.com/mapbox/mapbox-gl-native/pull/10245)
+
+# 3.5.7 - October 9, 2017
+- Fixed an issue causing synchronous resource requests to stall [#10153](https://github.com/mapbox/mapbox-gl-native/pull/10153)
+
# 3.5.6 - September 29, 2017
- Protects against requests which throw [#9554](https://github.com/mapbox/mapbox-gl-native/pull/9554)
- Fixed an issue around reusing a map object [#9554](https://github.com/mapbox/mapbox-gl-native/pull/9554)
diff --git a/platform/node/src/node_conversion.hpp b/platform/node/src/node_conversion.hpp
index d266745548..7c5bbf4386 100644
--- a/platform/node/src/node_conversion.hpp
+++ b/platform/node/src/node_conversion.hpp
@@ -9,111 +9,136 @@
#include <mbgl/util/optional.hpp>
#include <mbgl/util/feature.hpp>
#include <mbgl/style/conversion.hpp>
+#include <mbgl/style/conversion/geojson.hpp>
namespace mbgl {
namespace style {
namespace conversion {
-inline bool isUndefined(v8::Local<v8::Value> value) {
- Nan::HandleScope scope;
- return value->IsUndefined() || value->IsNull();
-}
+template <>
+class ConversionTraits<v8::Local<v8::Value>> {
+public:
+ static bool isUndefined(const v8::Local<v8::Value>& value) {
+ Nan::HandleScope scope;
+ return value->IsUndefined() || value->IsNull();
+ }
-inline bool isArray(v8::Local<v8::Value> value) {
- Nan::HandleScope scope;
- return value->IsArray();
-}
+ static bool isArray(const v8::Local<v8::Value>& value) {
+ Nan::HandleScope scope;
+ return value->IsArray();
+ }
-inline std::size_t arrayLength(v8::Local<v8::Value> value) {
- Nan::HandleScope scope;
- return value.As<v8::Array>()->Length();
-}
+ static std::size_t arrayLength(const v8::Local<v8::Value>& value) {
+ Nan::HandleScope scope;
+ // const_cast because v8::Local<T>::As is not marked const until node v8.0
+ v8::Local<v8::Array> array = const_cast<v8::Local<v8::Value>&>(value).As<v8::Array>();
+ return array->Length();
+ }
-inline v8::Local<v8::Value> arrayMember(v8::Local<v8::Value> value, std::size_t i) {
- Nan::EscapableHandleScope scope;
- return scope.Escape(Nan::Get(value.As<v8::Array>(), i).ToLocalChecked());
-}
+ static v8::Local<v8::Value> arrayMember(const v8::Local<v8::Value>& value, std::size_t i) {
+ Nan::EscapableHandleScope scope;
+ // const_cast because v8::Local<T>::As is not marked const until node v8.0
+ v8::Local<v8::Array> array = const_cast<v8::Local<v8::Value>&>(value).As<v8::Array>();
+ return scope.Escape(Nan::Get(array, i).ToLocalChecked());
+ }
-inline bool isObject(v8::Local<v8::Value> value) {
- Nan::HandleScope scope;
- return value->IsObject() && !value->IsArray();
-}
+ static bool isObject(const v8::Local<v8::Value>& value) {
+ Nan::HandleScope scope;
+ return value->IsObject() && !value->IsArray();
+ }
-inline optional<v8::Local<v8::Value>> objectMember(v8::Local<v8::Value> value, const char * name) {
- Nan::EscapableHandleScope scope;
- if (!Nan::Has(Nan::To<v8::Object>(value).ToLocalChecked(), Nan::New(name).ToLocalChecked()).FromJust()) {
- return {};
+ static optional<v8::Local<v8::Value>> objectMember(const v8::Local<v8::Value>& value, const char * name) {
+ Nan::EscapableHandleScope scope;
+ if (!Nan::Has(Nan::To<v8::Object>(value).ToLocalChecked(), Nan::New(name).ToLocalChecked()).FromJust()) {
+ return {};
+ }
+ Nan::MaybeLocal<v8::Value> result = Nan::Get(Nan::To<v8::Object>(value).ToLocalChecked(), Nan::New(name).ToLocalChecked());
+ if (result.IsEmpty()) {
+ return {};
+ }
+ return {scope.Escape(result.ToLocalChecked())};
}
- Nan::MaybeLocal<v8::Value> result = Nan::Get(Nan::To<v8::Object>(value).ToLocalChecked(), Nan::New(name).ToLocalChecked());
- if (result.IsEmpty()) {
+
+ template <class Fn>
+ static optional<Error> eachMember(const v8::Local<v8::Value>& value, Fn&& fn) {
+ Nan::HandleScope scope;
+ v8::Local<v8::Array> names = Nan::GetOwnPropertyNames(Nan::To<v8::Object>(value).ToLocalChecked()).ToLocalChecked();
+ for (uint32_t i = 0; i < names->Length(); ++i) {
+ v8::Local<v8::Value> k = Nan::Get(names, i).ToLocalChecked();
+ v8::Local<v8::Value> v = Nan::Get(Nan::To<v8::Object>(value).ToLocalChecked(), k).ToLocalChecked();
+ optional<Error> result = fn(*Nan::Utf8String(k), std::move(v));
+ if (result) {
+ return result;
+ }
+ }
return {};
}
- return scope.Escape(result.ToLocalChecked());
-}
-template <class Fn>
-optional<Error> eachMember(v8::Local<v8::Value> value, Fn&& fn) {
- Nan::HandleScope scope;
- v8::Local<v8::Array> names = Nan::GetOwnPropertyNames(Nan::To<v8::Object>(value).ToLocalChecked()).ToLocalChecked();
- for (uint32_t i = 0; i < names->Length(); ++i) {
- v8::Local<v8::Value> k = Nan::Get(names, i).ToLocalChecked();
- v8::Local<v8::Value> v = Nan::Get(Nan::To<v8::Object>(value).ToLocalChecked(), k).ToLocalChecked();
- optional<Error> result = fn(*Nan::Utf8String(k), v);
- if (result) {
- return result;
+ static optional<bool> toBool(const v8::Local<v8::Value>& value) {
+ Nan::HandleScope scope;
+ if (!value->IsBoolean()) {
+ return {};
}
+ return value->BooleanValue();
}
- return {};
-}
-inline optional<bool> toBool(v8::Local<v8::Value> value) {
- Nan::HandleScope scope;
- if (!value->IsBoolean()) {
- return {};
+ static optional<float> toNumber(const v8::Local<v8::Value>& value) {
+ Nan::HandleScope scope;
+ if (!value->IsNumber()) {
+ return {};
+ }
+ return value->NumberValue();
}
- return value->BooleanValue();
-}
-inline optional<float> toNumber(v8::Local<v8::Value> value) {
- Nan::HandleScope scope;
- if (!value->IsNumber()) {
- return {};
+ static optional<double> toDouble(const v8::Local<v8::Value>& value) {
+ Nan::HandleScope scope;
+ if (!value->IsNumber()) {
+ return {};
+ }
+ return value->NumberValue();
}
- return value->NumberValue();
-}
-inline optional<double> toDouble(v8::Local<v8::Value> value) {
- Nan::HandleScope scope;
- if (!value->IsNumber()) {
- return {};
+ static optional<std::string> toString(const v8::Local<v8::Value>& value) {
+ Nan::HandleScope scope;
+ if (!value->IsString()) {
+ return {};
+ }
+ return std::string(*Nan::Utf8String(value));
}
- return value->NumberValue();
-}
-inline optional<std::string> toString(v8::Local<v8::Value> value) {
- Nan::HandleScope scope;
- if (!value->IsString()) {
- return {};
+ static optional<Value> toValue(const v8::Local<v8::Value>& value) {
+ if (value->IsFalse()) {
+ return { false };
+ } else if (value->IsTrue()) {
+ return { true };
+ } else if (value->IsString()) {
+ return { std::string(*Nan::Utf8String(value)) };
+ } else if (value->IsUint32()) {
+ return { std::uint64_t(value->Uint32Value()) };
+ } else if (value->IsInt32()) {
+ return { std::int64_t(value->Int32Value()) };
+ } else if (value->IsNumber()) {
+ return { value->NumberValue() };
+ } else {
+ return {};
+ }
}
- return std::string(*Nan::Utf8String(value));
-}
-inline optional<Value> toValue(v8::Local<v8::Value> value) {
- if (value->IsFalse()) {
- return { false };
- } else if (value->IsTrue()) {
- return { true };
- } else if (value->IsString()) {
- return { std::string(*Nan::Utf8String(value)) };
- } else if (value->IsUint32()) {
- return { std::uint64_t(value->Uint32Value()) };
- } else if (value->IsInt32()) {
- return { std::int64_t(value->Int32Value()) };
- } else if (value->IsNumber()) {
- return { value->NumberValue() };
- } else {
- return {};
+ static optional<GeoJSON> toGeoJSON(const v8::Local<v8::Value>& value, Error& error) {
+ try {
+ Nan::JSON JSON;
+ std::string string = *Nan::Utf8String(JSON.Stringify(value->ToObject()).ToLocalChecked());
+ return parseGeoJSON(string, error);
+ } catch (const std::exception& ex) {
+ error = { ex.what() };
+ return {};
+ }
}
+};
+
+template <class T, class...Args>
+optional<T> convert(const v8::Local<v8::Value>& value, Error& error, Args&&...args) {
+ return convert<T>(Convertible(value), error, std::forward<Args>(args)...);
}
} // namespace conversion
diff --git a/platform/node/src/node_expression.cpp b/platform/node/src/node_expression.cpp
new file mode 100644
index 0000000000..8958d5c6c7
--- /dev/null
+++ b/platform/node/src/node_expression.cpp
@@ -0,0 +1,230 @@
+#include "node_conversion.hpp"
+#include "node_expression.hpp"
+
+#include <mbgl/style/expression/parsing_context.hpp>
+#include <mbgl/style/expression/is_constant.hpp>
+#include <mbgl/style/conversion/geojson.hpp>
+#include <mbgl/util/geojson.hpp>
+#include <nan.h>
+
+using namespace mbgl::style;
+using namespace mbgl::style::expression;
+
+namespace node_mbgl {
+
+Nan::Persistent<v8::Function> NodeExpression::constructor;
+
+void NodeExpression::Init(v8::Local<v8::Object> target) {
+ v8::Local<v8::FunctionTemplate> tpl = Nan::New<v8::FunctionTemplate>(New);
+ tpl->SetClassName(Nan::New("Expression").ToLocalChecked());
+ tpl->InstanceTemplate()->SetInternalFieldCount(1); // what is this doing?
+
+ Nan::SetPrototypeMethod(tpl, "evaluate", Evaluate);
+ Nan::SetPrototypeMethod(tpl, "getType", GetType);
+ Nan::SetPrototypeMethod(tpl, "isFeatureConstant", IsFeatureConstant);
+ Nan::SetPrototypeMethod(tpl, "isZoomConstant", IsZoomConstant);
+
+ Nan::SetMethod(tpl, "parse", Parse);
+
+ constructor.Reset(tpl->GetFunction()); // what is this doing?
+ Nan::Set(target, Nan::New("Expression").ToLocalChecked(), tpl->GetFunction());
+}
+
+type::Type parseType(v8::Local<v8::Object> type) {
+ static std::unordered_map<std::string, type::Type> types = {
+ {"string", type::String},
+ {"number", type::Number},
+ {"noolean", type::Boolean},
+ {"object", type::Object},
+ {"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());
+ }
+
+ auto expr = info[0];
+
+ try {
+ ParsingContext ctx(expected);
+ ParseResult parsed = ctx.parse(mbgl::style::conversion::Convertible(expr));
+ if (parsed) {
+ assert(ctx.getErrors().size() == 0);
+ auto nodeExpr = new NodeExpression(std::move(*parsed));
+ const int argc = 0;
+ v8::Local<v8::Value> argv[0] = {};
+ auto wrapped = Nan::NewInstance(cons, argc, argv).ToLocalChecked();
+ nodeExpr->Wrap(wrapped);
+ 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];
+ v8::Local<v8::Object> err = Nan::New<v8::Object>();
+ Nan::Set(err,
+ Nan::New("key").ToLocalChecked(),
+ Nan::New(error.key.c_str()).ToLocalChecked());
+ Nan::Set(err,
+ Nan::New("error").ToLocalChecked(),
+ Nan::New(error.message.c_str()).ToLocalChecked());
+ Nan::Set(result, Nan::New((uint32_t)i), err);
+ }
+ info.GetReturnValue().Set(result);
+ } catch(std::exception &ex) {
+ return Nan::ThrowError(ex.what());
+ }
+}
+
+void NodeExpression::New(const Nan::FunctionCallbackInfo<v8::Value>& info) {
+ if (!info.IsConstructCall()) {
+ return Nan::ThrowTypeError("Use the new operator to create new Expression objects");
+ }
+
+ info.GetReturnValue().Set(info.This());
+}
+
+struct ToValue {
+ v8::Local<v8::Value> operator()(mbgl::NullValue) {
+ Nan::EscapableHandleScope scope;
+ return scope.Escape(Nan::Null());
+ }
+
+ v8::Local<v8::Value> operator()(bool t) {
+ Nan::EscapableHandleScope scope;
+ return scope.Escape(Nan::New(t));
+ }
+
+ v8::Local<v8::Value> operator()(double t) {
+ Nan::EscapableHandleScope scope;
+ return scope.Escape(Nan::New(t));
+ }
+
+ v8::Local<v8::Value> operator()(const std::string& t) {
+ Nan::EscapableHandleScope scope;
+ return scope.Escape(Nan::New(t).ToLocalChecked());
+ }
+
+ v8::Local<v8::Value> operator()(const std::vector<Value>& array) {
+ Nan::EscapableHandleScope scope;
+ v8::Local<v8::Array> result = Nan::New<v8::Array>();
+ for (unsigned int i = 0; i < array.size(); i++) {
+ result->Set(i, toJS(array[i]));
+ }
+ return scope.Escape(result);
+ }
+
+ v8::Local<v8::Value> operator()(const mbgl::Color& color) {
+ return operator()(std::vector<Value> {
+ static_cast<double>(color.r),
+ static_cast<double>(color.g),
+ static_cast<double>(color.b),
+ static_cast<double>(color.a)
+ });
+ }
+
+ v8::Local<v8::Value> operator()(const std::unordered_map<std::string, Value>& map) {
+ Nan::EscapableHandleScope scope;
+ v8::Local<v8::Object> result = Nan::New<v8::Object>();
+ for (const auto& entry : map) {
+ Nan::Set(result, Nan::New(entry.first).ToLocalChecked(), toJS(entry.second));
+ }
+
+ return scope.Escape(result);
+ }
+};
+
+v8::Local<v8::Value> toJS(const Value& value) {
+ return Value::visit(value, ToValue());
+}
+
+void NodeExpression::Evaluate(const Nan::FunctionCallbackInfo<v8::Value>& info) {
+ NodeExpression* nodeExpr = ObjectWrap::Unwrap<NodeExpression>(info.Holder());
+ const std::unique_ptr<Expression>& expression = nodeExpr->expression;
+
+ if (info.Length() < 2 || !info[0]->IsObject()) {
+ return Nan::ThrowTypeError("Requires globals and feature arguments.");
+ }
+
+ mbgl::optional<float> zoom;
+ v8::Local<v8::Value> v8zoom = Nan::Get(info[0]->ToObject(), Nan::New("zoom").ToLocalChecked()).ToLocalChecked();
+ if (v8zoom->IsNumber()) zoom = v8zoom->NumberValue();
+
+ mbgl::optional<double> heatmapDensity;
+ v8::Local<v8::Value> v8heatmapDensity = Nan::Get(info[0]->ToObject(), Nan::New("heatmapDensity").ToLocalChecked()).ToLocalChecked();
+ if (v8heatmapDensity->IsNumber()) heatmapDensity = v8heatmapDensity->NumberValue();
+
+ Nan::JSON NanJSON;
+ conversion::Error conversionError;
+ mbgl::optional<mbgl::GeoJSON> geoJSON = conversion::convert<mbgl::GeoJSON>(info[1], conversionError);
+ if (!geoJSON) {
+ Nan::ThrowTypeError(conversionError.message.c_str());
+ return;
+ }
+
+ try {
+ mapbox::geojson::feature feature = geoJSON->get<mapbox::geojson::feature>();
+ auto result = expression->evaluate(zoom, feature, heatmapDensity);
+ if (result) {
+ info.GetReturnValue().Set(toJS(*result));
+ } else {
+ v8::Local<v8::Object> res = Nan::New<v8::Object>();
+ Nan::Set(res,
+ Nan::New("error").ToLocalChecked(),
+ Nan::New(result.error().message.c_str()).ToLocalChecked());
+ info.GetReturnValue().Set(res);
+ }
+ } catch(std::exception &ex) {
+ return Nan::ThrowTypeError(ex.what());
+ }
+}
+
+void NodeExpression::GetType(const Nan::FunctionCallbackInfo<v8::Value>& info) {
+ NodeExpression* nodeExpr = ObjectWrap::Unwrap<NodeExpression>(info.Holder());
+ const std::unique_ptr<Expression>& expression = nodeExpr->expression;
+
+ const type::Type type = expression->getType();
+ const std::string name = type.match([&] (const auto& t) { return t.getName(); });
+ info.GetReturnValue().Set(Nan::New(name.c_str()).ToLocalChecked());
+}
+
+void NodeExpression::IsFeatureConstant(const Nan::FunctionCallbackInfo<v8::Value>& info) {
+ NodeExpression* nodeExpr = ObjectWrap::Unwrap<NodeExpression>(info.Holder());
+ const std::unique_ptr<Expression>& expression = nodeExpr->expression;
+ info.GetReturnValue().Set(Nan::New(isFeatureConstant(*expression)));
+}
+
+void NodeExpression::IsZoomConstant(const Nan::FunctionCallbackInfo<v8::Value>& info) {
+ NodeExpression* nodeExpr = ObjectWrap::Unwrap<NodeExpression>(info.Holder());
+ const std::unique_ptr<Expression>& expression = nodeExpr->expression;
+ info.GetReturnValue().Set(Nan::New(isZoomConstant(*expression)));
+}
+
+} // namespace node_mbgl
diff --git a/platform/node/src/node_expression.hpp b/platform/node/src/node_expression.hpp
new file mode 100644
index 0000000000..7af5b7ab51
--- /dev/null
+++ b/platform/node/src/node_expression.hpp
@@ -0,0 +1,40 @@
+#pragma once
+
+#include <mbgl/style/conversion.hpp>
+#include <mbgl/style/expression/expression.hpp>
+#include <exception>
+#include <memory>
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-parameter"
+#pragma GCC diagnostic ignored "-Wshadow"
+#include <nan.h>
+#pragma GCC diagnostic pop
+
+using namespace mbgl::style::expression;
+
+namespace node_mbgl {
+
+v8::Local<v8::Value> toJS(const Value&);
+
+class NodeExpression : public Nan::ObjectWrap {
+public:
+ static void Init(v8::Local<v8::Object>);
+
+private:
+ NodeExpression(std::unique_ptr<Expression> expression_) :
+ expression(std::move(expression_))
+ {};
+
+ static void New(const Nan::FunctionCallbackInfo<v8::Value>&);
+ static void Parse(const Nan::FunctionCallbackInfo<v8::Value>&);
+ static void Evaluate(const Nan::FunctionCallbackInfo<v8::Value>&);
+ 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 Nan::Persistent<v8::Function> constructor;
+
+ std::unique_ptr<Expression> expression;
+};
+
+} // namespace node_mbgl
diff --git a/platform/node/src/node_geojson.hpp b/platform/node/src/node_geojson.hpp
deleted file mode 100644
index 8a3927e2cf..0000000000
--- a/platform/node/src/node_geojson.hpp
+++ /dev/null
@@ -1,17 +0,0 @@
-#include <mbgl/style/conversion/geojson.hpp>
-
-#include <string>
-namespace mbgl {
-namespace style {
-namespace conversion {
-
-template <>
-optional<GeoJSON> Converter<GeoJSON>::operator()(const v8::Local<v8::Value>& value, Error& error) const {
- Nan::JSON JSON;
- std::string string = *Nan::Utf8String(JSON.Stringify(value->ToObject()).ToLocalChecked());
- return convert<GeoJSON>(string, error);
-}
-
-} // namespace conversion
-} // namespace style
-} // namespace mbgl
diff --git a/platform/node/src/node_map.cpp b/platform/node/src/node_map.cpp
index 9a7ebce445..6deccf05bf 100644
--- a/platform/node/src/node_map.cpp
+++ b/platform/node/src/node_map.cpp
@@ -2,7 +2,6 @@
#include "node_request.hpp"
#include "node_feature.hpp"
#include "node_conversion.hpp"
-#include "node_geojson.hpp"
#include <mbgl/util/exception.hpp>
#include <mbgl/renderer/renderer.hpp>
@@ -10,6 +9,15 @@
#include <mbgl/style/conversion/source.hpp>
#include <mbgl/style/conversion/layer.hpp>
#include <mbgl/style/conversion/filter.hpp>
+
+#include <mbgl/style/layers/background_layer.hpp>
+#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/line_layer.hpp>
+#include <mbgl/style/layers/raster_layer.hpp>
+#include <mbgl/style/layers/symbol_layer.hpp>
+
#include <mbgl/style/style.hpp>
#include <mbgl/style/image.hpp>
#include <mbgl/map/map_observer.hpp>
@@ -26,6 +34,9 @@ struct NodeMap::RenderOptions {
double latitude = 0;
double longitude = 0;
mbgl::Size size = { 512, 512 };
+ bool axonometric = false;
+ double xSkew = 0;
+ double ySkew = 1;
std::vector<std::string> classes;
mbgl::MapDebugOptions debugOptions = mbgl::MapDebugOptions::NoDebug;
};
@@ -65,6 +76,9 @@ void NodeMap::Init(v8::Local<v8::Object> target) {
Nan::SetPrototypeMethod(tpl, "setZoom", SetZoom);
Nan::SetPrototypeMethod(tpl, "setBearing", SetBearing);
Nan::SetPrototypeMethod(tpl, "setPitch", SetPitch);
+ Nan::SetPrototypeMethod(tpl, "setAxonometric", SetAxonometric);
+ Nan::SetPrototypeMethod(tpl, "setXSkew", SetXSkew);
+ Nan::SetPrototypeMethod(tpl, "setYSkew", SetYSkew);
Nan::SetPrototypeMethod(tpl, "dumpDebugLogs", DumpDebugLogs);
Nan::SetPrototypeMethod(tpl, "queryRenderedFeatures", QueryRenderedFeatures);
@@ -251,6 +265,19 @@ NodeMap::RenderOptions NodeMap::ParseOptions(v8::Local<v8::Object> obj) {
options.pitch = Nan::Get(obj, Nan::New("pitch").ToLocalChecked()).ToLocalChecked()->NumberValue();
}
+ if (Nan::Has(obj, Nan::New("axonometric").ToLocalChecked()).FromJust()) {
+ options.axonometric = Nan::Get(obj, Nan::New("axonometric").ToLocalChecked()).ToLocalChecked()->BooleanValue();
+ }
+
+ if (Nan::Has(obj, Nan::New("skew").ToLocalChecked()).FromJust()) {
+ auto skewObj = Nan::Get(obj, Nan::New("skew").ToLocalChecked()).ToLocalChecked();
+ if (skewObj->IsArray()) {
+ auto skew = skewObj.As<v8::Array>();
+ if (skew->Length() > 0) { options.xSkew = Nan::Get(skew, 0).ToLocalChecked()->NumberValue(); }
+ if (skew->Length() > 1) { options.ySkew = Nan::Get(skew, 1).ToLocalChecked()->NumberValue(); }
+ }
+ }
+
if (Nan::Has(obj, Nan::New("center").ToLocalChecked()).FromJust()) {
auto centerObj = Nan::Get(obj, Nan::New("center").ToLocalChecked()).ToLocalChecked();
if (centerObj->IsArray()) {
@@ -370,6 +397,18 @@ void NodeMap::startRender(NodeMap::RenderOptions options) {
camera.angle = -options.bearing * mbgl::util::DEG2RAD;
camera.pitch = options.pitch * mbgl::util::DEG2RAD;
+ if (map->getAxonometric() != options.axonometric) {
+ map->setAxonometric(options.axonometric);
+ }
+
+ if (map->getXSkew() != options.xSkew) {
+ map->setXSkew(options.xSkew);
+ }
+
+ if (map->getYSkew() != options.ySkew) {
+ map->setYSkew(options.ySkew);
+ }
+
map->renderStill(camera, options.debugOptions, [this](const std::exception_ptr eptr) {
if (eptr) {
error = std::move(eptr);
@@ -477,6 +516,7 @@ void NodeMap::release() {
});
map.reset();
+ frontend.reset();
}
/**
@@ -709,7 +749,7 @@ void NodeMap::SetLayoutProperty(const Nan::FunctionCallbackInfo<v8::Value>& info
return Nan::ThrowTypeError("Second argument must be a string");
}
- mbgl::optional<Error> error = setLayoutProperty(*layer, *Nan::Utf8String(info[1]), info[2]);
+ mbgl::optional<Error> error = setLayoutProperty(*layer, *Nan::Utf8String(info[1]), Convertible(info[2]));
if (error) {
return Nan::ThrowTypeError(error->message.c_str());
}
@@ -741,7 +781,7 @@ void NodeMap::SetPaintProperty(const Nan::FunctionCallbackInfo<v8::Value>& info)
return Nan::ThrowTypeError("Second argument must be a string");
}
- mbgl::optional<Error> error = setPaintProperty(*layer, *Nan::Utf8String(info[1]), info[2]);
+ mbgl::optional<Error> error = setPaintProperty(*layer, *Nan::Utf8String(info[1]), Convertible(info[2]));
if (error) {
return Nan::ThrowTypeError(error->message.c_str());
}
@@ -879,6 +919,57 @@ void NodeMap::SetPitch(const Nan::FunctionCallbackInfo<v8::Value>& info) {
info.GetReturnValue().SetUndefined();
}
+void NodeMap::SetAxonometric(const Nan::FunctionCallbackInfo<v8::Value>& info) {
+ auto nodeMap = Nan::ObjectWrap::Unwrap<NodeMap>(info.Holder());
+ if (!nodeMap->map) return Nan::ThrowError(releasedMessage());
+
+ if (info.Length() <= 0 || !info[0]->IsBoolean()) {
+ return Nan::ThrowTypeError("First argument must be a boolean");
+ }
+
+ try {
+ nodeMap->map->setAxonometric(info[0]->BooleanValue());
+ } catch (const std::exception &ex) {
+ return Nan::ThrowError(ex.what());
+ }
+
+ info.GetReturnValue().SetUndefined();
+}
+
+void NodeMap::SetXSkew(const Nan::FunctionCallbackInfo<v8::Value>& info) {
+ auto nodeMap = Nan::ObjectWrap::Unwrap<NodeMap>(info.Holder());
+ if (!nodeMap->map) return Nan::ThrowError(releasedMessage());
+
+ if (info.Length() <= 0 || !info[0]->IsNumber()) {
+ return Nan::ThrowTypeError("First argument must be a number");
+ }
+
+ try {
+ nodeMap->map->setXSkew(info[0]->NumberValue());
+ } catch (const std::exception &ex) {
+ return Nan::ThrowError(ex.what());
+ }
+
+ info.GetReturnValue().SetUndefined();
+}
+
+void NodeMap::SetYSkew(const Nan::FunctionCallbackInfo<v8::Value>& info) {
+ auto nodeMap = Nan::ObjectWrap::Unwrap<NodeMap>(info.Holder());
+ if (!nodeMap->map) return Nan::ThrowError(releasedMessage());
+
+ if (info.Length() <= 0 || !info[0]->IsNumber()) {
+ return Nan::ThrowTypeError("First argument must be a number");
+ }
+
+ try {
+ nodeMap->map->setYSkew(info[0]->NumberValue());
+ } catch (const std::exception &ex) {
+ return Nan::ThrowError(ex.what());
+ }
+
+ info.GetReturnValue().SetUndefined();
+}
+
void NodeMap::DumpDebugLogs(const Nan::FunctionCallbackInfo<v8::Value>& info) {
auto nodeMap = Nan::ObjectWrap::Unwrap<NodeMap>(info.Holder());
if (!nodeMap->map) return Nan::ThrowError(releasedMessage());
diff --git a/platform/node/src/node_map.hpp b/platform/node/src/node_map.hpp
index c8e33bb347..8e7f0a3e58 100644
--- a/platform/node/src/node_map.hpp
+++ b/platform/node/src/node_map.hpp
@@ -57,6 +57,9 @@ public:
static void SetZoom(const Nan::FunctionCallbackInfo<v8::Value>&);
static void SetBearing(const Nan::FunctionCallbackInfo<v8::Value>&);
static void SetPitch(const Nan::FunctionCallbackInfo<v8::Value>&);
+ static void SetAxonometric(const Nan::FunctionCallbackInfo<v8::Value>&);
+ static void SetXSkew(const Nan::FunctionCallbackInfo<v8::Value>&);
+ static void SetYSkew(const Nan::FunctionCallbackInfo<v8::Value>&);
static void DumpDebugLogs(const Nan::FunctionCallbackInfo<v8::Value>&);
static void QueryRenderedFeatures(const Nan::FunctionCallbackInfo<v8::Value>&);
diff --git a/platform/node/src/node_mapbox_gl_native.cpp b/platform/node/src/node_mapbox_gl_native.cpp
index cdcc982220..96e96e4298 100644
--- a/platform/node/src/node_mapbox_gl_native.cpp
+++ b/platform/node/src/node_mapbox_gl_native.cpp
@@ -10,6 +10,7 @@
#include "node_map.hpp"
#include "node_logging.hpp"
#include "node_request.hpp"
+#include "node_expression.hpp"
void RegisterModule(v8::Local<v8::Object> target, v8::Local<v8::Object> module) {
// This has the effect of:
@@ -20,6 +21,7 @@ void RegisterModule(v8::Local<v8::Object> target, v8::Local<v8::Object> module)
node_mbgl::NodeMap::Init(target);
node_mbgl::NodeRequest::Init();
+ node_mbgl::NodeExpression::Init(target);
// Exports Resource constants.
v8::Local<v8::Object> resource = Nan::New<v8::Object>();
diff --git a/platform/node/test/expression.test.js b/platform/node/test/expression.test.js
new file mode 100644
index 0000000000..aac039ce18
--- /dev/null
+++ b/platform/node/test/expression.test.js
@@ -0,0 +1,71 @@
+'use strict';
+
+var suite = require('../../../mapbox-gl-js/test/integration').expression;
+var mbgl = require('../index');
+var ignores = require('./ignores.json');
+
+var tests;
+
+if (process.argv[1] === __filename && process.argv.length > 2) {
+ tests = process.argv.slice(2);
+}
+
+function getExpectedType(spec) {
+ if (spec.type === 'array') {
+ const itemType = getExpectedType({ type: spec.value });
+ const array = {
+ kind: 'array',
+ itemType: itemType || { kind: 'value' },
+ };
+ if (typeof spec.length === 'number') {
+ array.N = spec.length;
+ }
+ return array;
+ }
+
+ if (spec.type === 'enum') {
+ return { kind: 'string' };
+ }
+
+ return typeof spec.type === 'string' ? {kind: spec.type} : null;
+}
+
+suite.run('native', {ignores: ignores, tests: tests}, (fixture) => {
+ const compiled = {};
+ const result = {
+ compiled
+ };
+
+ 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 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);
+ }
+
+ if (fixture.inputs) {
+ result.outputs = evaluateResults;
+ }
+ } else {
+ compiled.result = 'error';
+ compiled.errors = expression;
+ }
+
+ return result;
+});
+
diff --git a/platform/node/test/ignores.json b/platform/node/test/ignores.json
index 1084a5c923..dd9679cde4 100644
--- a/platform/node/test/ignores.json
+++ b/platform/node/test/ignores.json
@@ -1,50 +1,88 @@
{
+ "expression-tests/curve/step": "https://github.com/mapbox/mapbox-gl-js/issues/5580",
+ "expression-tests/curve/interpolate": "https://github.com/mapbox/mapbox-gl-js/issues/5580",
+ "query-tests/circle-stroke-width/inside": "https://github.com/mapbox/mapbox-gl-native/issues/10307",
"query-tests/geometry/multilinestring": "needs investigation",
"query-tests/geometry/multipolygon": "needs investigation",
"query-tests/geometry/polygon": "needs investigation",
"query-tests/regressions/mapbox-gl-js#3534": "https://github.com/mapbox/mapbox-gl-native/issues/8193",
"query-tests/regressions/mapbox-gl-js#4417": "https://github.com/mapbox/mapbox-gl-native/issues/8007",
+ "query-tests/regressions/mapbox-gl-js#5554": "https://github.com/mapbox/mapbox-gl-native/pull/10103",
+ "query-tests/symbol/panned-after-insert": "https://github.com/mapbox/mapbox-gl-native/pull/10103",
+ "query-tests/symbol/rotated-after-insert": "https://github.com/mapbox/mapbox-gl-native/pull/10103",
+ "query-tests/symbol/rotated-inside": "https://github.com/mapbox/mapbox-gl-native/pull/10103",
"query-tests/symbol-features-in/pitched-screen": "https://github.com/mapbox/mapbox-gl-native/issues/6817",
"query-tests/symbol-features-in/tilted-inside": "https://github.com/mapbox/mapbox-gl-native/issues/5056",
"query-tests/symbol-features-in/tilted-outside": "https://github.com/mapbox/mapbox-gl-native/issues/9435",
"query-tests/world-wrapping/box": "skip - needs issue",
"query-tests/world-wrapping/point": "skip - needs issue",
+ "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/pull/10103",
+ "render-tests/debug/collision-lines-pitched": "https://github.com/mapbox/mapbox-gl-native/pull/10103",
"render-tests/debug/collision-overscaled": "https://github.com/mapbox/mapbox-gl-native/issues/3841",
- "render-tests/debug/collision-pitched-wrapped": "https://github.com/mapbox/mapbox-gl-native/issues/3841",
"render-tests/debug/collision-pitched": "https://github.com/mapbox/mapbox-gl-native/issues/3841",
- "render-tests/debug/collision": "https://github.com/mapbox/mapbox-gl-native/issues/3841",
- "render-tests/debug/tile-overscaled": "https://github.com/mapbox/mapbox-gl-native/issues/3841",
+ "render-tests/debug/collision-pitched-wrapped": "https://github.com/mapbox/mapbox-gl-native/issues/3841",
"render-tests/debug/tile": "https://github.com/mapbox/mapbox-gl-native/issues/3841",
+ "render-tests/debug/tile-overscaled": "https://github.com/mapbox/mapbox-gl-native/issues/3841",
"render-tests/extent/1024-circle": "needs investigation",
"render-tests/extent/1024-symbol": "needs investigation",
"render-tests/fill-extrusion-pattern/@2x": "https://github.com/mapbox/mapbox-gl-js/issues/3327",
- "render-tests/fill-extrusion-pattern/function-2": "https://github.com/mapbox/mapbox-gl-js/issues/3327",
"render-tests/fill-extrusion-pattern/function": "https://github.com/mapbox/mapbox-gl-js/issues/3327",
+ "render-tests/fill-extrusion-pattern/function-2": "https://github.com/mapbox/mapbox-gl-js/issues/3327",
"render-tests/fill-extrusion-pattern/literal": "https://github.com/mapbox/mapbox-gl-js/issues/3327",
"render-tests/fill-extrusion-pattern/missing": "https://github.com/mapbox/mapbox-gl-js/issues/3327",
"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/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/icon-size/composite-function-high-base-plain": "https://github.com/mapbox/mapbox-gl-native/issues/8654",
"render-tests/icon-size/composite-function-high-base-sdf": "https://github.com/mapbox/mapbox-gl-native/issues/8654",
- "render-tests/icon-text-fit/both-padding": "https://github.com/mapbox/mapbox-gl-native/issues/5602",
"render-tests/icon-text-fit/both": "https://github.com/mapbox/mapbox-gl-native/issues/5602",
+ "render-tests/icon-text-fit/both-padding": "https://github.com/mapbox/mapbox-gl-native/issues/5602",
"render-tests/icon-text-fit/height": "https://github.com/mapbox/mapbox-gl-native/issues/5602",
- "render-tests/icon-text-fit/width-padding": "https://github.com/mapbox/mapbox-gl-native/issues/5602",
+ "render-tests/icon-text-fit/placement-line": "https://github.com/mapbox/mapbox-gl-native/pull/10103",
"render-tests/icon-text-fit/width": "https://github.com/mapbox/mapbox-gl-native/issues/5602",
- "render-tests/line-width/property-function": "https://github.com/mapbox/mapbox-gl-js/issues/3682#issuecomment-264348200",
+ "render-tests/icon-text-fit/width-padding": "https://github.com/mapbox/mapbox-gl-native/issues/5602",
"render-tests/line-join/property-function": "https://github.com/mapbox/mapbox-gl-js/pull/5020",
"render-tests/line-join/property-function-dasharray": "https://github.com/mapbox/mapbox-gl-js/pull/5020",
"render-tests/line-opacity/step-curve": "https://github.com/mapbox/mapbox-gl-native/pull/9439",
+ "render-tests/line-width/property-function": "https://github.com/mapbox/mapbox-gl-js/issues/3682#issuecomment-264348200",
+ "render-tests/mixed-zoom/z10-z11": "https://github.com/mapbox/mapbox-gl-native/pull/10103",
+ "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",
"render-tests/regressions/mapbox-gl-js#3682": "https://github.com/mapbox/mapbox-gl-js/issues/3682",
+ "render-tests/regressions/mapbox-gl-js#4647": "https://github.com/mapbox/mapbox-gl-native/pull/10103",
+ "render-tests/regressions/mapbox-gl-js#5370": "skip - https://github.com/mapbox/mapbox-gl-native/pull/9439",
+ "render-tests/regressions/mapbox-gl-js#5599": "https://github.com/mapbox/mapbox-gl-native/issues/10399",
"render-tests/regressions/mapbox-gl-native#7357": "https://github.com/mapbox/mapbox-gl-native/issues/7357",
"render-tests/runtime-styling/image-add-sdf": "https://github.com/mapbox/mapbox-gl-native/issues/9847",
"render-tests/runtime-styling/paint-property-fill-flat-to-extrude": "https://github.com/mapbox/mapbox-gl-native/issues/6745",
+ "render-tests/runtime-styling/set-style-glyphs": "https://github.com/mapbox/mapbox-gl-native/pull/10103",
"render-tests/runtime-styling/set-style-paint-property-fill-flat-to-extrude": "https://github.com/mapbox/mapbox-gl-native/issues/6745",
"render-tests/symbol-placement/line": "needs issue",
+ "render-tests/symbol-placement/line-overscaled": "https://github.com/mapbox/mapbox-gl-native/pull/10103",
+ "render-tests/symbol-placement/point": "https://github.com/mapbox/mapbox-gl-native/pull/10103",
+ "render-tests/symbol-spacing/line-close": "https://github.com/mapbox/mapbox-gl-native/pull/10103",
+ "render-tests/symbol-spacing/line-far": "https://github.com/mapbox/mapbox-gl-native/pull/10103",
+ "render-tests/symbol-visibility/visible": "https://github.com/mapbox/mapbox-gl-native/pull/10103",
"render-tests/text-font/camera-function": "https://github.com/mapbox/mapbox-gl-native/pull/9439",
- "render-tests/text-keep-upright/line-placement-true-offset": "https://github.com/mapbox/mapbox-gl-native/issues/9271",
+ "render-tests/text-font/chinese": "https://github.com/mapbox/mapbox-gl-native/pull/10103",
"render-tests/text-pitch-alignment/auto-text-rotation-alignment-map": "https://github.com/mapbox/mapbox-gl-native/issues/9732",
"render-tests/text-pitch-alignment/auto-text-rotation-alignment-viewport": "https://github.com/mapbox/mapbox-gl-native/issues/9732",
"render-tests/text-pitch-alignment/map-text-rotation-alignment-map": "https://github.com/mapbox/mapbox-gl-native/issues/9732",
@@ -55,5 +93,7 @@
"render-tests/text-pitch-alignment/viewport-text-rotation-alignment-viewport": "https://github.com/mapbox/mapbox-gl-native/issues/9732",
"render-tests/text-pitch-scaling/line-half": "https://github.com/mapbox/mapbox-gl-native/issues/9732",
"render-tests/text-size/composite-expression": "https://github.com/mapbox/mapbox-gl-native/pull/9439",
+ "render-tests/text-tile-edge-clipping/default": "https://github.com/mapbox/mapbox-gl-native/pull/10103",
+ "render-tests/text-visibility/visible": "https://github.com/mapbox/mapbox-gl-native/pull/10103",
"render-tests/video/default": "skip - https://github.com/mapbox/mapbox-gl-native/issues/601"
}
diff --git a/platform/node/test/js/map.test.js b/platform/node/test/js/map.test.js
index 39665e41ef..e3c8031908 100644
--- a/platform/node/test/js/map.test.js
+++ b/platform/node/test/js/map.test.js
@@ -121,6 +121,9 @@ test('Map', function(t) {
'setZoom',
'setBearing',
'setPitch',
+ 'setAxonometric',
+ 'setXSkew',
+ 'setYSkew',
'dumpDebugLogs',
'queryRenderedFeatures'
]);
diff --git a/platform/node/test/suite_implementation.js b/platform/node/test/suite_implementation.js
index 323f429bed..c5987538d0 100644
--- a/platform/node/test/suite_implementation.js
+++ b/platform/node/test/suite_implementation.js
@@ -90,6 +90,12 @@ module.exports = function (style, options, callback) {
applyOperations(operations.slice(1), callback);
});
+ } else if (operation[0] === 'sleep') {
+ // Prefer "wait", which renders until the map is loaded
+ // Use "sleep" when you need to test something that sidesteps the "loaded" logic
+ setTimeout(() => {
+ applyOperations(operations.slice(1), callback);
+ }, operation[1]);
} else if (operation[0] === 'addImage' || operation[0] === 'updateImage') {
var img = PNG.sync.read(fs.readFileSync(path.join(__dirname, '../../../mapbox-gl-js/test/integration', operation[2])));
const testOpts = (operation.length > 3) ? operation[3] : {};
diff --git a/platform/qt/config.cmake b/platform/qt/config.cmake
index a7fdbf3542..e3b15fddfd 100644
--- a/platform/qt/config.cmake
+++ b/platform/qt/config.cmake
@@ -20,6 +20,7 @@ macro(mbgl_platform_core)
target_include_directories(mbgl-core
PUBLIC platform/default
+ PRIVATE platform/qt
PRIVATE platform/qt/include
)
@@ -69,6 +70,10 @@ macro(mbgl_platform_test)
PRIVATE platform/qt/test/qmapboxgl.test.cpp
)
+ target_include_directories(mbgl-test
+ PRIVATE platform/qt
+ )
+
set_source_files_properties(
platform/qt/test/main.cpp
PROPERTIES COMPILE_FLAGS -DWORK_DIRECTORY="${CMAKE_SOURCE_DIR}"
diff --git a/platform/qt/include/qmapboxgl.hpp b/platform/qt/include/qmapboxgl.hpp
index e2fb283989..ebbd949921 100644
--- a/platform/qt/include/qmapboxgl.hpp
+++ b/platform/qt/include/qmapboxgl.hpp
@@ -10,6 +10,8 @@
#include <QString>
#include <QStringList>
+#include <functional>
+
class QMapboxGLPrivate;
// This header follows the Qt coding style: https://wiki.qt.io/Qt_Coding_Style
@@ -59,6 +61,9 @@ public:
QString apiBaseUrl() const;
void setApiBaseUrl(const QString &);
+ std::function<std::string(const std::string &&)> resourceTransform() const;
+ void setResourceTransform(const std::function<std::string(const std::string &&)> &);
+
private:
GLContextMode m_contextMode;
ConstrainMode m_constrainMode;
@@ -69,6 +74,7 @@ private:
QString m_assetPath;
QString m_accessToken;
QString m_apiBaseUrl;
+ std::function<std::string(const std::string &&)> m_resourceTransform;
};
struct Q_DECL_EXPORT QMapboxGLCameraOptions {
diff --git a/platform/qt/mbgl/gl/gl_impl.hpp b/platform/qt/mbgl/gl/gl_impl.hpp
new file mode 100644
index 0000000000..a9f720db7c
--- /dev/null
+++ b/platform/qt/mbgl/gl/gl_impl.hpp
@@ -0,0 +1,173 @@
+#pragma once
+
+#include <QtGlobal>
+
+// Qt4
+#if QT_VERSION < 0x050000
+ #if MBGL_USE_GLES2
+ #define GL_GLEXT_PROTOTYPES
+ #include <GLES2/gl2.h>
+ #include <GLES2/gl2ext.h>
+ #else
+ #define GL_GLEXT_PROTOTYPES
+ #include <GL/gl.h>
+ #include <GL/glext.h>
+ #endif
+
+// Qt5
+#else
+ #include <QOpenGLContext>
+ #include <QOpenGLFunctions>
+
+ #ifndef GL_RGBA8_OES
+ #define GL_RGBA8_OES GL_RGBA8
+ #endif
+
+ #ifndef GL_DEPTH24_STENCIL8_OES
+ #define GL_DEPTH24_STENCIL8_OES GL_DEPTH24_STENCIL8
+ #endif
+
+ #define glActiveTexture(...) QOpenGLContext::currentContext()->functions()->glActiveTexture(__VA_ARGS__)
+ #define glAttachShader(...) QOpenGLContext::currentContext()->functions()->glAttachShader(__VA_ARGS__)
+ #define glBindAttribLocation(...) QOpenGLContext::currentContext()->functions()->glBindAttribLocation(__VA_ARGS__)
+ #define glBindBuffer(...) QOpenGLContext::currentContext()->functions()->glBindBuffer(__VA_ARGS__)
+ #define glBindFramebuffer(...) QOpenGLContext::currentContext()->functions()->glBindFramebuffer(__VA_ARGS__)
+ #define glBindRenderbuffer(...) QOpenGLContext::currentContext()->functions()->glBindRenderbuffer(__VA_ARGS__)
+ #define glBindTexture(...) QOpenGLContext::currentContext()->functions()->glBindTexture(__VA_ARGS__)
+ #define glBlendColor(...) QOpenGLContext::currentContext()->functions()->glBlendColor(__VA_ARGS__)
+ #define glBlendEquation(...) QOpenGLContext::currentContext()->functions()->glBlendEquation(__VA_ARGS__)
+ #define glBlendEquationSeparate(...) QOpenGLContext::currentContext()->functions()->glBlendEquationSeparate(__VA_ARGS__)
+ #define glBlendFunc(...) QOpenGLContext::currentContext()->functions()->glBlendFunc(__VA_ARGS__)
+ #define glBlendFuncSeparate(...) QOpenGLContext::currentContext()->functions()->glBlendFuncSeparate(__VA_ARGS__)
+ #define glBufferData(...) QOpenGLContext::currentContext()->functions()->glBufferData(__VA_ARGS__)
+ #define glBufferSubData(...) QOpenGLContext::currentContext()->functions()->glBufferSubData(__VA_ARGS__)
+ #define glCheckFramebufferStatus(...) QOpenGLContext::currentContext()->functions()->glCheckFramebufferStatus(__VA_ARGS__)
+ #define glClear(...) QOpenGLContext::currentContext()->functions()->glClear(__VA_ARGS__)
+ #define glClearColor(...) QOpenGLContext::currentContext()->functions()->glClearColor(__VA_ARGS__)
+ #define glClearDepthf(...) QOpenGLContext::currentContext()->functions()->glClearDepthf(__VA_ARGS__)
+ #define glClearStencil(...) QOpenGLContext::currentContext()->functions()->glClearStencil(__VA_ARGS__)
+ #define glColorMask(...) QOpenGLContext::currentContext()->functions()->glColorMask(__VA_ARGS__)
+ #define glCompileShader(...) QOpenGLContext::currentContext()->functions()->glCompileShader(__VA_ARGS__)
+ #define glCompressedTexImage2D(...) QOpenGLContext::currentContext()->functions()->glCompressedTexImage2D(__VA_ARGS__)
+ #define glCompressedTexSubImage2D(...) QOpenGLContext::currentContext()->functions()->glCompressedTexSubImage2D(__VA_ARGS__)
+ #define glCopyTexImage2D(...) QOpenGLContext::currentContext()->functions()->glCopyTexImage2D(__VA_ARGS__)
+ #define glCopyTexSubImage2D(...) QOpenGLContext::currentContext()->functions()->glCopyTexSubImage2D(__VA_ARGS__)
+ #define glCreateProgram(...) QOpenGLContext::currentContext()->functions()->glCreateProgram(__VA_ARGS__)
+ #define glCreateShader(...) QOpenGLContext::currentContext()->functions()->glCreateShader(__VA_ARGS__)
+ #define glCullFace(...) QOpenGLContext::currentContext()->functions()->glCullFace(__VA_ARGS__)
+ #define glDeleteBuffers(...) QOpenGLContext::currentContext()->functions()->glDeleteBuffers(__VA_ARGS__)
+ #define glDeleteFramebuffers(...) QOpenGLContext::currentContext()->functions()->glDeleteFramebuffers(__VA_ARGS__)
+ #define glDeleteProgram(...) QOpenGLContext::currentContext()->functions()->glDeleteProgram(__VA_ARGS__)
+ #define glDeleteRenderbuffers(...) QOpenGLContext::currentContext()->functions()->glDeleteRenderbuffers(__VA_ARGS__)
+ #define glDeleteShader(...) QOpenGLContext::currentContext()->functions()->glDeleteShader(__VA_ARGS__)
+ #define glDeleteTextures(...) QOpenGLContext::currentContext()->functions()->glDeleteTextures(__VA_ARGS__)
+ #define glDepthFunc(...) QOpenGLContext::currentContext()->functions()->glDepthFunc(__VA_ARGS__)
+ #define glDepthMask(...) QOpenGLContext::currentContext()->functions()->glDepthMask(__VA_ARGS__)
+ #define glDepthRangef(...) QOpenGLContext::currentContext()->functions()->glDepthRangef(__VA_ARGS__)
+ #define glDetachShader(...) QOpenGLContext::currentContext()->functions()->glDetachShader(__VA_ARGS__)
+ #define glDisable(...) QOpenGLContext::currentContext()->functions()->glDisable(__VA_ARGS__)
+ #define glDisableVertexAttribArray(...) QOpenGLContext::currentContext()->functions()->glDisableVertexAttribArray(__VA_ARGS__)
+ #define glDrawArrays(...) QOpenGLContext::currentContext()->functions()->glDrawArrays(__VA_ARGS__)
+ #define glDrawElements(...) QOpenGLContext::currentContext()->functions()->glDrawElements(__VA_ARGS__)
+ #define glEnable(...) QOpenGLContext::currentContext()->functions()->glEnable(__VA_ARGS__)
+ #define glEnableVertexAttribArray(...) QOpenGLContext::currentContext()->functions()->glEnableVertexAttribArray(__VA_ARGS__)
+ #define glFinish(...) QOpenGLContext::currentContext()->functions()->glFinish(__VA_ARGS__)
+ #define glFlush(...) QOpenGLContext::currentContext()->functions()->glFlush(__VA_ARGS__)
+ #define glFramebufferRenderbuffer(...) QOpenGLContext::currentContext()->functions()->glFramebufferRenderbuffer(__VA_ARGS__)
+ #define glFramebufferTexture2D(...) QOpenGLContext::currentContext()->functions()->glFramebufferTexture2D(__VA_ARGS__)
+ #define glFrontFace(...) QOpenGLContext::currentContext()->functions()->glFrontFace(__VA_ARGS__)
+ #define glGenBuffers(...) QOpenGLContext::currentContext()->functions()->glGenBuffers(__VA_ARGS__)
+ #define glGenerateMipmap(...) QOpenGLContext::currentContext()->functions()->glGenerateMipmap(__VA_ARGS__)
+ #define glGenFramebuffers(...) QOpenGLContext::currentContext()->functions()->glGenFramebuffers(__VA_ARGS__)
+ #define glGenRenderbuffers(...) QOpenGLContext::currentContext()->functions()->glGenRenderbuffers(__VA_ARGS__)
+ #define glGenTextures(...) QOpenGLContext::currentContext()->functions()->glGenTextures(__VA_ARGS__)
+ #define glGetActiveAttrib(...) QOpenGLContext::currentContext()->functions()->glGetActiveAttrib(__VA_ARGS__)
+ #define glGetActiveUniform(...) QOpenGLContext::currentContext()->functions()->glGetActiveUniform(__VA_ARGS__)
+ #define glGetAttachedShaders(...) QOpenGLContext::currentContext()->functions()->glGetAttachedShaders(__VA_ARGS__)
+ #define glGetAttribLocation(...) QOpenGLContext::currentContext()->functions()->glGetAttribLocation(__VA_ARGS__)
+ #define glGetBooleanv(...) QOpenGLContext::currentContext()->functions()->glGetBooleanv(__VA_ARGS__)
+ #define glGetBufferParameteriv(...) QOpenGLContext::currentContext()->functions()->glGetBufferParameteriv(__VA_ARGS__)
+ #define glGetError(...) QOpenGLContext::currentContext()->functions()->glGetError(__VA_ARGS__)
+ #define glGetFloatv(...) QOpenGLContext::currentContext()->functions()->glGetFloatv(__VA_ARGS__)
+ #define glGetFramebufferAttachmentParameteriv(...) QOpenGLContext::currentContext()->functions()->glGetFramebufferAttachmentParameteriv(__VA_ARGS__)
+ #define glGetIntegerv(...) QOpenGLContext::currentContext()->functions()->glGetIntegerv(__VA_ARGS__)
+ #define glGetProgramInfoLog(...) QOpenGLContext::currentContext()->functions()->glGetProgramInfoLog(__VA_ARGS__)
+ #define glGetProgramiv(...) QOpenGLContext::currentContext()->functions()->glGetProgramiv(__VA_ARGS__)
+ #define glGetRenderbufferParameteriv(...) QOpenGLContext::currentContext()->functions()->glGetRenderbufferParameteriv(__VA_ARGS__)
+ #define glGetShaderInfoLog(...) QOpenGLContext::currentContext()->functions()->glGetShaderInfoLog(__VA_ARGS__)
+ #define glGetShaderiv(...) QOpenGLContext::currentContext()->functions()->glGetShaderiv(__VA_ARGS__)
+ #define glGetShaderPrecisionFormat(...) QOpenGLContext::currentContext()->functions()->glGetShaderPrecisionFormat(__VA_ARGS__)
+ #define glGetShaderSource(...) QOpenGLContext::currentContext()->functions()->glGetShaderSource(__VA_ARGS__)
+ #define glGetString(...) QOpenGLContext::currentContext()->functions()->glGetString(__VA_ARGS__)
+ #define glGetTexParameterfv(...) QOpenGLContext::currentContext()->functions()->glGetTexParameterfv(__VA_ARGS__)
+ #define glGetTexParameteriv(...) QOpenGLContext::currentContext()->functions()->glGetTexParameteriv(__VA_ARGS__)
+ #define glGetUniformfv(...) QOpenGLContext::currentContext()->functions()->glGetUniformfv(__VA_ARGS__)
+ #define glGetUniformiv(...) QOpenGLContext::currentContext()->functions()->glGetUniformiv(__VA_ARGS__)
+ #define glGetUniformLocation(...) QOpenGLContext::currentContext()->functions()->glGetUniformLocation(__VA_ARGS__)
+ #define glGetVertexAttribfv(...) QOpenGLContext::currentContext()->functions()->glGetVertexAttribfv(__VA_ARGS__)
+ #define glGetVertexAttribiv(...) QOpenGLContext::currentContext()->functions()->glGetVertexAttribiv(__VA_ARGS__)
+ #define glGetVertexAttribPointerv(...) QOpenGLContext::currentContext()->functions()->glGetVertexAttribPointerv(__VA_ARGS__)
+ #define glHint(...) QOpenGLContext::currentContext()->functions()->glHint(__VA_ARGS__)
+ #define glIsBuffer(...) QOpenGLContext::currentContext()->functions()->glIsBuffer(__VA_ARGS__)
+ #define glIsEnabled(...) QOpenGLContext::currentContext()->functions()->glIsEnabled(__VA_ARGS__)
+ #define glIsFramebuffer(...) QOpenGLContext::currentContext()->functions()->glIsFramebuffer(__VA_ARGS__)
+ #define glIsProgram(...) QOpenGLContext::currentContext()->functions()->glIsProgram(__VA_ARGS__)
+ #define glIsRenderbuffer(...) QOpenGLContext::currentContext()->functions()->glIsRenderbuffer(__VA_ARGS__)
+ #define glIsShader(...) QOpenGLContext::currentContext()->functions()->glIsShader(__VA_ARGS__)
+ #define glIsTexture(...) QOpenGLContext::currentContext()->functions()->glIsTexture(__VA_ARGS__)
+ #define glLineWidth(...) QOpenGLContext::currentContext()->functions()->glLineWidth(__VA_ARGS__)
+ #define glLinkProgram(...) QOpenGLContext::currentContext()->functions()->glLinkProgram(__VA_ARGS__)
+ #define glPixelStorei(...) QOpenGLContext::currentContext()->functions()->glPixelStorei(__VA_ARGS__)
+ #define glPolygonOffset(...) QOpenGLContext::currentContext()->functions()->glPolygonOffset(__VA_ARGS__)
+ #define glReadPixels(...) QOpenGLContext::currentContext()->functions()->glReadPixels(__VA_ARGS__)
+ #define glReleaseShaderCompiler(...) QOpenGLContext::currentContext()->functions()->glReleaseShaderCompiler(__VA_ARGS__)
+ #define glRenderbufferStorage(...) QOpenGLContext::currentContext()->functions()->glRenderbufferStorage(__VA_ARGS__)
+ #define glSampleCoverage(...) QOpenGLContext::currentContext()->functions()->glSampleCoverage(__VA_ARGS__)
+ #define glScissor(...) QOpenGLContext::currentContext()->functions()->glScissor(__VA_ARGS__)
+ #define glShaderBinary(...) QOpenGLContext::currentContext()->functions()->glShaderBinary(__VA_ARGS__)
+ #define glShaderSource(...) QOpenGLContext::currentContext()->functions()->glShaderSource(__VA_ARGS__)
+ #define glStencilFunc(...) QOpenGLContext::currentContext()->functions()->glStencilFunc(__VA_ARGS__)
+ #define glStencilFuncSeparate(...) QOpenGLContext::currentContext()->functions()->glStencilFuncSeparate(__VA_ARGS__)
+ #define glStencilMask(...) QOpenGLContext::currentContext()->functions()->glStencilMask(__VA_ARGS__)
+ #define glStencilMaskSeparate(...) QOpenGLContext::currentContext()->functions()->glStencilMaskSeparate(__VA_ARGS__)
+ #define glStencilOp(...) QOpenGLContext::currentContext()->functions()->glStencilOp(__VA_ARGS__)
+ #define glStencilOpSeparate(...) QOpenGLContext::currentContext()->functions()->glStencilOpSeparate(__VA_ARGS__)
+ #define glTexImage2D(...) QOpenGLContext::currentContext()->functions()->glTexImage2D(__VA_ARGS__)
+ #define glTexLevelParameteriv(...) QOpenGLContext::currentContext()->functions()->glTexLevelParameteriv(__VA_ARGS__)
+ #define glTexParameterf(...) QOpenGLContext::currentContext()->functions()->glTexParameterf(__VA_ARGS__)
+ #define glTexParameterfv(...) QOpenGLContext::currentContext()->functions()->glTexParameterfv(__VA_ARGS__)
+ #define glTexParameteri(...) QOpenGLContext::currentContext()->functions()->glTexParameteri(__VA_ARGS__)
+ #define glTexParameteriv(...) QOpenGLContext::currentContext()->functions()->glTexParameteriv(__VA_ARGS__)
+ #define glTexSubImage2D(...) QOpenGLContext::currentContext()->functions()->glTexSubImage2D(__VA_ARGS__)
+ #define glUniform1f(...) QOpenGLContext::currentContext()->functions()->glUniform1f(__VA_ARGS__)
+ #define glUniform1fv(...) QOpenGLContext::currentContext()->functions()->glUniform1fv(__VA_ARGS__)
+ #define glUniform1i(...) QOpenGLContext::currentContext()->functions()->glUniform1i(__VA_ARGS__)
+ #define glUniform1iv(...) QOpenGLContext::currentContext()->functions()->glUniform1iv(__VA_ARGS__)
+ #define glUniform2f(...) QOpenGLContext::currentContext()->functions()->glUniform2f(__VA_ARGS__)
+ #define glUniform2fv(...) QOpenGLContext::currentContext()->functions()->glUniform2fv(__VA_ARGS__)
+ #define glUniform2i(...) QOpenGLContext::currentContext()->functions()->glUniform2i(__VA_ARGS__)
+ #define glUniform2iv(...) QOpenGLContext::currentContext()->functions()->glUniform2iv(__VA_ARGS__)
+ #define glUniform3f(...) QOpenGLContext::currentContext()->functions()->glUniform3f(__VA_ARGS__)
+ #define glUniform3fv(...) QOpenGLContext::currentContext()->functions()->glUniform3fv(__VA_ARGS__)
+ #define glUniform3i(...) QOpenGLContext::currentContext()->functions()->glUniform3i(__VA_ARGS__)
+ #define glUniform3iv(...) QOpenGLContext::currentContext()->functions()->glUniform3iv(__VA_ARGS__)
+ #define glUniform4f(...) QOpenGLContext::currentContext()->functions()->glUniform4f(__VA_ARGS__)
+ #define glUniform4fv(...) QOpenGLContext::currentContext()->functions()->glUniform4fv(__VA_ARGS__)
+ #define glUniform4i(...) QOpenGLContext::currentContext()->functions()->glUniform4i(__VA_ARGS__)
+ #define glUniform4iv(...) QOpenGLContext::currentContext()->functions()->glUniform4iv(__VA_ARGS__)
+ #define glUniformMatrix2fv(...) QOpenGLContext::currentContext()->functions()->glUniformMatrix2fv(__VA_ARGS__)
+ #define glUniformMatrix3fv(...) QOpenGLContext::currentContext()->functions()->glUniformMatrix3fv(__VA_ARGS__)
+ #define glUniformMatrix4fv(...) QOpenGLContext::currentContext()->functions()->glUniformMatrix4fv(__VA_ARGS__)
+ #define glUseProgram(...) QOpenGLContext::currentContext()->functions()->glUseProgram(__VA_ARGS__)
+ #define glValidateProgram(...) QOpenGLContext::currentContext()->functions()->glValidateProgram(__VA_ARGS__)
+ #define glVertexAttrib1f(...) QOpenGLContext::currentContext()->functions()->glVertexAttrib1f(__VA_ARGS__)
+ #define glVertexAttrib1fv(...) QOpenGLContext::currentContext()->functions()->glVertexAttrib1fv(__VA_ARGS__)
+ #define glVertexAttrib2f(...) QOpenGLContext::currentContext()->functions()->glVertexAttrib2f(__VA_ARGS__)
+ #define glVertexAttrib2fv(...) QOpenGLContext::currentContext()->functions()->glVertexAttrib2fv(__VA_ARGS__)
+ #define glVertexAttrib3f(...) QOpenGLContext::currentContext()->functions()->glVertexAttrib3f(__VA_ARGS__)
+ #define glVertexAttrib3fv(...) QOpenGLContext::currentContext()->functions()->glVertexAttrib3fv(__VA_ARGS__)
+ #define glVertexAttrib4f(...) QOpenGLContext::currentContext()->functions()->glVertexAttrib4f(__VA_ARGS__)
+ #define glVertexAttrib4fv(...) QOpenGLContext::currentContext()->functions()->glVertexAttrib4fv(__VA_ARGS__)
+ #define glVertexAttribPointer(...) QOpenGLContext::currentContext()->functions()->glVertexAttribPointer(__VA_ARGS__)
+ #define glViewport(...) QOpenGLContext::currentContext()->functions()->glViewport(__VA_ARGS__)
+#endif
diff --git a/platform/qt/qt.cmake b/platform/qt/qt.cmake
index 2346d7d820..f832dd593c 100644
--- a/platform/qt/qt.cmake
+++ b/platform/qt/qt.cmake
@@ -60,6 +60,9 @@ set(MBGL_QT_FILESOURCE_FILES
add_library(qmapboxgl SHARED
platform/qt/include/qmapbox.hpp
platform/qt/include/qmapboxgl.hpp
+ platform/qt/src/qt_conversion.hpp
+ platform/qt/src/qt_geojson.cpp
+ platform/qt/src/qt_geojson.hpp
platform/qt/src/qmapbox.cpp
platform/qt/src/qmapboxgl.cpp
platform/qt/src/qmapboxgl_p.hpp
@@ -103,12 +106,6 @@ endif()
xcode_create_scheme(TARGET mbgl-qt)
-if(WITH_QT_4)
- include(platform/qt/qt4.cmake)
-else()
- include(platform/qt/qt5.cmake)
-endif()
-
# OS specific configurations
if (MASON_PLATFORM STREQUAL "osx" OR MASON_PLATFORM STREQUAL "ios")
list(APPEND MBGL_QT_CORE_FILES
@@ -116,15 +113,21 @@ if (MASON_PLATFORM STREQUAL "osx" OR MASON_PLATFORM STREQUAL "ios")
)
list(APPEND MBGL_QT_CORE_LIBRARIES
PRIVATE "-framework Foundation"
- PRIVATE "-framework OpenGL"
)
+ if(WITH_QT_4)
+ list(APPEND MBGL_QT_CORE_LIBRARIES
+ PRIVATE "-framework OpenGL"
+ )
+ endif()
elseif (CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux")
list(APPEND MBGL_QT_CORE_FILES
PRIVATE platform/default/thread.cpp
)
- list(APPEND MBGL_QT_CORE_LIBRARIES
- PRIVATE -lGL
- )
+ if(WITH_QT_4)
+ list(APPEND MBGL_QT_CORE_LIBRARIES
+ PRIVATE "-lGL"
+ )
+ endif()
elseif (CMAKE_HOST_SYSTEM_NAME STREQUAL "Windows")
list(APPEND MBGL_QT_CORE_FILES
PRIVATE platform/qt/src/thread.cpp
diff --git a/platform/qt/qt5.cmake b/platform/qt/qt5.cmake
index c4af774ba3..0c9a974b4b 100644
--- a/platform/qt/qt5.cmake
+++ b/platform/qt/qt5.cmake
@@ -5,6 +5,12 @@ find_package(Qt5OpenGL REQUIRED)
find_package(Qt5Widgets REQUIRED)
find_package(Qt5Sql REQUIRED)
+# Qt5 always build OpenGL ES2 which is the compatibility
+# mode. Qt5 will take care of translating the desktop
+# version of OpenGL to ES2.
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DMBGL_USE_GLES2")
+set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DMBGL_USE_GLES2")
+
set(MBGL_QT_CORE_LIBRARIES
PUBLIC Qt5::Core
PUBLIC Qt5::Gui
diff --git a/platform/qt/src/http_file_source.cpp b/platform/qt/src/http_file_source.cpp
index 573b707c27..6e70693241 100644
--- a/platform/qt/src/http_file_source.cpp
+++ b/platform/qt/src/http_file_source.cpp
@@ -80,9 +80,10 @@ void HTTPFileSource::Impl::onReplyFinished()
return;
}
+ QByteArray data = reply->readAll();
QVector<HTTPRequest*>& requestsVector = it.value().second;
for (auto req : requestsVector) {
- req->handleNetworkReply(reply);
+ req->handleNetworkReply(reply, data);
}
m_pending.erase(it);
diff --git a/platform/qt/src/http_request.cpp b/platform/qt/src/http_request.cpp
index 386a2d9ef4..ea3f388bd5 100644
--- a/platform/qt/src/http_request.cpp
+++ b/platform/qt/src/http_request.cpp
@@ -52,7 +52,7 @@ QNetworkRequest HTTPRequest::networkRequest() const
return req;
}
-void HTTPRequest::handleNetworkReply(QNetworkReply *reply)
+void HTTPRequest::handleNetworkReply(QNetworkReply *reply, const QByteArray& data)
{
m_handled = true;
@@ -98,11 +98,10 @@ void HTTPRequest::handleNetworkReply(QNetworkReply *reply)
switch(responseCode) {
case 200: {
- QByteArray bytes = reply->readAll();
- if (bytes.isEmpty()) {
+ if (data.isEmpty()) {
response.data = std::make_shared<std::string>();
} else {
- response.data = std::make_shared<std::string>(bytes.constData(), bytes.size());
+ response.data = std::make_shared<std::string>(data.constData(), data.size());
}
break;
}
diff --git a/platform/qt/src/http_request.hpp b/platform/qt/src/http_request.hpp
index 959f97759a..b4d476d586 100644
--- a/platform/qt/src/http_request.hpp
+++ b/platform/qt/src/http_request.hpp
@@ -20,7 +20,7 @@ public:
QUrl requestUrl() const;
QNetworkRequest networkRequest() const;
- void handleNetworkReply(QNetworkReply *);
+ void handleNetworkReply(QNetworkReply *, const QByteArray& data);
private:
HTTPFileSource::Impl* m_context;
diff --git a/platform/qt/src/qmapboxgl.cpp b/platform/qt/src/qmapboxgl.cpp
index e79c6af56a..fc281fdf1d 100644
--- a/platform/qt/src/qmapboxgl.cpp
+++ b/platform/qt/src/qmapboxgl.cpp
@@ -14,7 +14,17 @@
#include <mbgl/style/conversion.hpp>
#include <mbgl/style/conversion/layer.hpp>
#include <mbgl/style/conversion/source.hpp>
+#include <mbgl/style/conversion/filter.hpp>
+#include <mbgl/style/conversion/geojson.hpp>
+#include <mbgl/style/filter.hpp>
#include <mbgl/style/layers/custom_layer.hpp>
+#include <mbgl/style/layers/background_layer.hpp>
+#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/line_layer.hpp>
+#include <mbgl/style/layers/raster_layer.hpp>
+#include <mbgl/style/layers/symbol_layer.hpp>
#include <mbgl/style/sources/geojson_source.hpp>
#include <mbgl/style/transition_options.hpp>
#include <mbgl/style/image.hpp>
@@ -29,6 +39,7 @@
#include <mbgl/util/run_loop.hpp>
#include <mbgl/util/shared_thread_pool.hpp>
#include <mbgl/util/traits.hpp>
+#include <mbgl/actor/scheduler.hpp>
#if QT_VERSION >= 0x050000
#include <QGuiApplication>
@@ -362,6 +373,27 @@ void QMapboxGLSettings::setApiBaseUrl(const QString& url)
}
/*!
+ Returns resource transformation callback used to transform requested URLs.
+*/
+std::function<std::string(const std::string &&)> QMapboxGLSettings::resourceTransform() const
+{
+ return m_resourceTransform;
+}
+
+/*!
+ Sets the resource transformation callback.
+
+ When given, resource transformation callback will be used to transform the
+ requested resource URLs before they are requested from internet. This can be
+ used add or remove custom parameters, or reroute certain requests to other
+ servers or endpoints.
+*/
+void QMapboxGLSettings::setResourceTransform(const std::function<std::string(const std::string &&)> &transform)
+{
+ m_resourceTransform = transform;
+}
+
+/*!
\class QMapboxGL
\brief The QMapboxGL class is a Qt wrapper for the Mapbox GL Native engine.
@@ -1499,6 +1531,18 @@ QMapboxGLPrivate::QMapboxGLPrivate(QMapboxGL *q, const QMapboxGLSettings &settin
settings.cacheDatabaseMaximumSize()))
, threadPool(mbgl::sharedThreadPool())
{
+ // Setup resource transform if needed
+ 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());
+ }
+
// Setup and connect the renderer frontend
frontend = std::make_unique<QMapboxGLRendererFrontend>(
std::make_unique<mbgl::Renderer>(*this, pixelRatio, *fileSourceObj, *threadPool,
@@ -1534,7 +1578,9 @@ mbgl::Size QMapboxGLPrivate::getFramebufferSize() const {
void QMapboxGLPrivate::updateAssumedState() {
assumeFramebufferBinding(fbObject);
+#if QT_VERSION >= 0x050600
assumeViewport(0, 0, getFramebufferSize());
+#endif
}
void QMapboxGLPrivate::bind() {
diff --git a/platform/qt/src/qmapboxgl_p.hpp b/platform/qt/src/qmapboxgl_p.hpp
index eb86870c10..5e12b44a20 100644
--- a/platform/qt/src/qmapboxgl_p.hpp
+++ b/platform/qt/src/qmapboxgl_p.hpp
@@ -3,15 +3,19 @@
#include "qmapboxgl.hpp"
#include "qmapboxgl_renderer_frontend_p.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/storage/default_file_source.hpp>
#include <mbgl/util/geo.hpp>
+#include <mbgl/storage/resource_transform.hpp>
#include <QObject>
#include <QSize>
+#include <memory>
+
class QMapboxGLPrivate : public QObject, public mbgl::RendererBackend, public mbgl::MapObserver
{
Q_OBJECT
@@ -68,4 +72,7 @@ signals:
void needsRendering();
void mapChanged(QMapboxGL::MapChange);
void copyrightsChanged(const QString &copyrightsHtml);
+
+private:
+ std::unique_ptr< mbgl::Actor<mbgl::ResourceTransform> > m_resourceTransform;
};
diff --git a/platform/qt/src/qt_conversion.hpp b/platform/qt/src/qt_conversion.hpp
index 40d7e5b928..19b0cb54fc 100644
--- a/platform/qt/src/qt_conversion.hpp
+++ b/platform/qt/src/qt_conversion.hpp
@@ -1,120 +1,145 @@
#pragma once
#include <mbgl/style/conversion.hpp>
-#include <mbgl/util/feature.hpp>
+#include <mbgl/style/conversion/geojson.hpp>
#include <mbgl/util/optional.hpp>
-#include <QMapbox>
-
-#include <QColor>
#include <QVariant>
+#include <QColor>
+#include <QMapbox>
+#include "qt_geojson.hpp"
namespace mbgl {
namespace style {
namespace conversion {
-inline bool isUndefined(const QVariant& value) {
- return value.isNull() || !value.isValid();
-}
+template <>
+class ConversionTraits<QVariant> {
+public:
+ static bool isUndefined(const QVariant& value) {
+ return value.isNull() || !value.isValid();
+ }
-inline bool isArray(const QVariant& value) {
- return value.canConvert(QVariant::List);
-}
+ static bool isArray(const QVariant& value) {
+ return value.canConvert(QVariant::List);
+ }
-inline std::size_t arrayLength(const QVariant& value) {
- return value.toList().size();
-}
+ static std::size_t arrayLength(const QVariant& value) {
+ return value.toList().size();
+ }
-inline QVariant arrayMember(const QVariant& value, std::size_t i) {
- return value.toList()[i];
-}
+ static QVariant arrayMember(const QVariant& value, std::size_t i) {
+ return value.toList()[i];
+ }
-inline bool isObject(const QVariant& value) {
- return value.canConvert(QVariant::Map)
- || value.type() == QVariant::ByteArray
-#if QT_VERSION >= 0x050000
- || QString(value.typeName()) == QStringLiteral("QMapbox::Feature");
-#else
- || QString(value.typeName()) == QString("QMapbox::Feature");
-#endif
-}
+ static bool isObject(const QVariant& value) {
+ return value.canConvert(QVariant::Map)
+ || value.type() == QVariant::ByteArray
+ #if QT_VERSION >= 0x050000
+ || QString(value.typeName()) == QStringLiteral("QMapbox::Feature");
+ #else
+ || QString(value.typeName()) == QString("QMapbox::Feature");
+ #endif
+ }
-inline optional<QVariant> objectMember(const QVariant& value, const char* key) {
- auto map = value.toMap();
- auto iter = map.constFind(key);
+ static optional<QVariant> objectMember(const QVariant& value, const char* key) {
+ auto map = value.toMap();
+ auto iter = map.constFind(key);
- if (iter != map.constEnd()) {
- return iter.value();
- } else {
- return {};
+ if (iter != map.constEnd()) {
+ return iter.value();
+ } else {
+ return {};
+ }
}
-}
-using EachMemberFn = std::function<optional<Error>(const std::string&, const QVariant&)>;
+ template <class Fn>
+ static optional<Error> eachMember(const QVariant& value, Fn&& fn) {
+ auto map = value.toMap();
+ auto iter = map.constBegin();
-optional<Error> eachMember(const QVariant& value, EachMemberFn&& fn) {
- auto map = value.toMap();
- auto iter = map.constBegin();
+ while (iter != map.constEnd()) {
+ optional<Error> result = fn(iter.key().toStdString(), QVariant(iter.value()));
+ if (result) {
+ return result;
+ }
- while (iter != map.constEnd()) {
- optional<Error> result = fn(iter.key().toStdString(), iter.value());
- if (result) {
- return result;
+ ++iter;
}
- ++iter;
+ return {};
}
- return {};
-}
+ static optional<bool> toBool(const QVariant& value) {
+ if (value.type() == QVariant::Bool) {
+ return value.toBool();
+ } else {
+ return {};
+ }
+ }
-inline optional<bool> toBool(const QVariant& value) {
- if (value.type() == QVariant::Bool) {
- return value.toBool();
- } else {
- return {};
+ static optional<float> toNumber(const QVariant& value) {
+ if (value.type() == QVariant::Int || value.type() == QVariant::Double) {
+ return value.toFloat();
+ } else {
+ return {};
+ }
}
-}
-inline optional<float> toNumber(const QVariant& value) {
- if (value.type() == QVariant::Int || value.type() == QVariant::Double) {
- return value.toFloat();
- } else {
- return {};
+ static optional<double> toDouble(const QVariant& value) {
+ if (value.type() == QVariant::Int || value.type() == QVariant::Double) {
+ return value.toDouble();
+ } else {
+ return {};
+ }
}
-}
-inline optional<double> toDouble(const QVariant& value) {
- if (value.type() == QVariant::Int || value.type() == QVariant::Double) {
- return value.toDouble();
- } else {
- return {};
+
+ static optional<std::string> toString(const QVariant& value) {
+ if (value.type() == QVariant::String) {
+ return value.toString().toStdString();
+ } else if (value.type() == QVariant::Color) {
+ return value.value<QColor>().name().toStdString();
+ } else {
+ return {};
+ }
}
-}
-inline optional<std::string> toString(const QVariant& value) {
- if (value.type() == QVariant::String) {
- return value.toString().toStdString();
- } else if (value.type() == QVariant::Color) {
- return value.value<QColor>().name().toStdString();
- } else {
- return {};
+ static optional<Value> toValue(const QVariant& value) {
+ if (value.type() == QVariant::Bool) {
+ return { value.toBool() };
+ } else if (value.type() == QVariant::String) {
+ return { value.toString().toStdString() };
+ } else if (value.type() == QVariant::Color) {
+ return { value.value<QColor>().name().toStdString() };
+ } else if (value.type() == QVariant::Int) {
+ return { int64_t(value.toInt()) };
+ } else if (value.canConvert(QVariant::Double)) {
+ return { value.toDouble() };
+ } else {
+ return {};
+ }
}
-}
-inline optional<Value> toValue(const QVariant& value) {
- if (value.type() == QVariant::Bool) {
- return { value.toBool() };
- } else if (value.type() == QVariant::String) {
- return { value.toString().toStdString() };
- } else if (value.type() == QVariant::Color) {
- return { value.value<QColor>().name().toStdString() };
- } else if (value.type() == QVariant::Int) {
- return { int64_t(value.toInt()) };
- } else if (value.canConvert(QVariant::Double)) {
- return { value.toDouble() };
- } else {
- return {};
+ static optional<GeoJSON> toGeoJSON(const QVariant& value, Error& error) {
+ #if QT_VERSION >= 0x050000
+ if (value.typeName() == QStringLiteral("QMapbox::Feature")) {
+ #else
+ if (value.typeName() == QString("QMapbox::Feature")) {
+ #endif
+ return GeoJSON { asMapboxGLFeature(value.value<QMapbox::Feature>()) };
+ } else if (value.type() != QVariant::ByteArray) {
+ error = { "JSON data must be in QByteArray" };
+ return {};
+ }
+
+ QByteArray data = value.toByteArray();
+ return parseGeoJSON(std::string(data.constData(), data.size()), error);
}
+};
+
+template <class T, class...Args>
+optional<T> convert(const QVariant& value, Error& error, Args&&...args) {
+ return convert<T>(Convertible(value), error, std::forward<Args>(args)...);
}
} // namespace conversion
diff --git a/platform/qt/src/qt_geojson.cpp b/platform/qt/src/qt_geojson.cpp
new file mode 100644
index 0000000000..80377de64d
--- /dev/null
+++ b/platform/qt/src/qt_geojson.cpp
@@ -0,0 +1,166 @@
+#include "qt_geojson.hpp"
+#include <mapbox/geojson.hpp>
+#include <mbgl/util/geometry.hpp>
+#include <mbgl/util/feature.hpp>
+
+namespace QMapbox {
+
+mbgl::Point<double> asMapboxGLPoint(const QMapbox::Coordinate &coordinate) {
+ return mbgl::Point<double> { coordinate.second, coordinate.first };
+}
+
+mbgl::MultiPoint<double> asMapboxGLMultiPoint(const QMapbox::Coordinates &multiPoint) {
+ mbgl::MultiPoint<double> mbglMultiPoint;
+ mbglMultiPoint.reserve(multiPoint.size());
+ for (const auto &point: multiPoint) {
+ mbglMultiPoint.emplace_back(asMapboxGLPoint(point));
+ }
+ return mbglMultiPoint;
+};
+
+mbgl::LineString<double> asMapboxGLLineString(const QMapbox::Coordinates &lineString) {
+ mbgl::LineString<double> mbglLineString;
+ mbglLineString.reserve(lineString.size());
+ for (const auto &coordinate : lineString) {
+ mbglLineString.emplace_back(asMapboxGLPoint(coordinate));
+ }
+ return mbglLineString;
+};
+
+mbgl::MultiLineString<double> asMapboxGLMultiLineString(const QMapbox::CoordinatesCollection &multiLineString) {
+ mbgl::MultiLineString<double> mbglMultiLineString;
+ mbglMultiLineString.reserve(multiLineString.size());
+ for (const auto &lineString : multiLineString) {
+ mbglMultiLineString.emplace_back(std::forward<mbgl::LineString<double>>(asMapboxGLLineString(lineString)));
+ }
+ return mbglMultiLineString;
+};
+
+mbgl::Polygon<double> asMapboxGLPolygon(const QMapbox::CoordinatesCollection &polygon) {
+ mbgl::Polygon<double> mbglPolygon;
+ mbglPolygon.reserve(polygon.size());
+ for (const auto &linearRing : polygon) {
+ mbgl::LinearRing<double> mbglLinearRing;
+ mbglLinearRing.reserve(linearRing.size());
+ for (const auto &coordinate: linearRing) {
+ mbglLinearRing.emplace_back(asMapboxGLPoint(coordinate));
+ }
+ mbglPolygon.emplace_back(std::move(mbglLinearRing));
+ }
+ return mbglPolygon;
+};
+
+mbgl::MultiPolygon<double> asMapboxGLMultiPolygon(const QMapbox::CoordinatesCollections &multiPolygon) {
+ mbgl::MultiPolygon<double> mbglMultiPolygon;
+ mbglMultiPolygon.reserve(multiPolygon.size());
+ for (const auto &polygon : multiPolygon) {
+ mbglMultiPolygon.emplace_back(std::forward<mbgl::Polygon<double>>(asMapboxGLPolygon(polygon)));
+ }
+ return mbglMultiPolygon;
+};
+
+mbgl::Value asMapboxGLPropertyValue(const QVariant &value) {
+ auto valueList = [](const QVariantList &list) {
+ std::vector<mbgl::Value> mbglList;
+ mbglList.reserve(list.size());
+ for (const auto& listValue : list) {
+ mbglList.emplace_back(asMapboxGLPropertyValue(listValue));
+ }
+ return mbglList;
+ };
+
+ auto valueMap = [](const QVariantMap &map) {
+ std::unordered_map<std::string, mbgl::Value> mbglMap;
+ mbglMap.reserve(map.size());
+ auto it = map.constBegin();
+ while (it != map.constEnd()) {
+ mbglMap.emplace(std::make_pair(it.key().toStdString(), asMapboxGLPropertyValue(it.value())));
+ ++it;
+ }
+ return mbglMap;
+ };
+
+ switch (value.type()) {
+#if QT_VERSION >= 0x050000
+ case QMetaType::UnknownType:
+#else
+ case QVariant::Invalid:
+#endif
+ return mbgl::NullValue {};
+ case QMetaType::Bool:
+ return { value.toBool() };
+ case QMetaType::ULongLong:
+ return { uint64_t(value.toULongLong()) };
+ case QMetaType::LongLong:
+ return { int64_t(value.toLongLong()) };
+ case QMetaType::Double:
+ return { value.toDouble() };
+ case QMetaType::QString:
+ return { value.toString().toStdString() };
+ case QMetaType::QVariantList:
+ return valueList(value.toList());
+ case QMetaType::QVariantMap:
+ return valueMap(value.toMap());
+ default:
+ qWarning() << "Unsupported feature property value:" << value;
+ return {};
+ }
+}
+
+mbgl::FeatureIdentifier asMapboxGLFeatureIdentifier(const QVariant &id) {
+ switch (id.type()) {
+#if QT_VERSION >= 0x050000
+ case QMetaType::UnknownType:
+#else
+ case QVariant::Invalid:
+#endif
+ return {};
+ case QMetaType::ULongLong:
+ return { uint64_t(id.toULongLong()) };
+ case QMetaType::LongLong:
+ return { int64_t(id.toLongLong()) };
+ case QMetaType::Double:
+ return { id.toDouble() };
+ case QMetaType::QString:
+ return { id.toString().toStdString() };
+ default:
+ qWarning() << "Unsupported feature identifier:" << id;
+ return {};
+ }
+}
+
+mbgl::Feature asMapboxGLFeature(const QMapbox::Feature &feature) {
+ mbgl::PropertyMap properties;
+ properties.reserve(feature.properties.size());
+ auto it = feature.properties.constBegin();
+ while (it != feature.properties.constEnd()) {
+ properties.emplace(std::make_pair(it.key().toStdString(), asMapboxGLPropertyValue(it.value())));
+ }
+
+ mbgl::FeatureIdentifier id = asMapboxGLFeatureIdentifier(feature.id);
+
+ if (feature.type == QMapbox::Feature::PointType) {
+ const QMapbox::Coordinates &points = feature.geometry.first().first();
+ if (points.size() == 1) {
+ return { asMapboxGLPoint(points.first()), std::move(properties), std::move(id) };
+ } else {
+ return { asMapboxGLMultiPoint(points), std::move(properties), std::move(id) };
+ }
+ } else if (feature.type == QMapbox::Feature::LineStringType) {
+ const QMapbox::CoordinatesCollection &lineStrings = feature.geometry.first();
+ if (lineStrings.size() == 1) {
+ return { asMapboxGLLineString(lineStrings.first()), std::move(properties), std::move(id) };
+ } else {
+ return { asMapboxGLMultiLineString(lineStrings), std::move(properties), std::move(id) };
+ }
+ } else { // PolygonType
+ const QMapbox::CoordinatesCollections &polygons = feature.geometry;
+ if (polygons.size() == 1) {
+ return { asMapboxGLPolygon(polygons.first()), std::move(properties), std::move(id) };
+ } else {
+ return { asMapboxGLMultiPolygon(polygons), std::move(properties), std::move(id) };
+ }
+ }
+};
+
+} // namespace QMapbox
diff --git a/platform/qt/src/qt_geojson.hpp b/platform/qt/src/qt_geojson.hpp
index a6958b7edc..a9c10272ab 100644
--- a/platform/qt/src/qt_geojson.hpp
+++ b/platform/qt/src/qt_geojson.hpp
@@ -1,7 +1,8 @@
#pragma once
#include <mapbox/geojson.hpp>
-#include <mbgl/style/conversion/geojson.hpp>
+#include <mbgl/util/geometry.hpp>
+#include <mbgl/util/feature.hpp>
#include <QMapbox>
@@ -13,187 +14,14 @@
namespace QMapbox {
-mbgl::Point<double> asMapboxGLPoint(const QMapbox::Coordinate &coordinate) {
- return mbgl::Point<double> { coordinate.second, coordinate.first };
-}
-
-mbgl::MultiPoint<double> asMapboxGLMultiPoint(const QMapbox::Coordinates &multiPoint) {
- mbgl::MultiPoint<double> mbglMultiPoint;
- mbglMultiPoint.reserve(multiPoint.size());
- for (const auto &point: multiPoint) {
- mbglMultiPoint.emplace_back(asMapboxGLPoint(point));
- }
- return mbglMultiPoint;
-};
-
-mbgl::LineString<double> asMapboxGLLineString(const QMapbox::Coordinates &lineString) {
- mbgl::LineString<double> mbglLineString;
- mbglLineString.reserve(lineString.size());
- for (const auto &coordinate : lineString) {
- mbglLineString.emplace_back(asMapboxGLPoint(coordinate));
- }
- return mbglLineString;
-};
-
-mbgl::MultiLineString<double> asMapboxGLMultiLineString(const QMapbox::CoordinatesCollection &multiLineString) {
- mbgl::MultiLineString<double> mbglMultiLineString;
- mbglMultiLineString.reserve(multiLineString.size());
- for (const auto &lineString : multiLineString) {
- mbglMultiLineString.emplace_back(std::forward<mbgl::LineString<double>>(asMapboxGLLineString(lineString)));
- }
- return mbglMultiLineString;
-};
-
-mbgl::Polygon<double> asMapboxGLPolygon(const QMapbox::CoordinatesCollection &polygon) {
- mbgl::Polygon<double> mbglPolygon;
- mbglPolygon.reserve(polygon.size());
- for (const auto &linearRing : polygon) {
- mbgl::LinearRing<double> mbglLinearRing;
- mbglLinearRing.reserve(linearRing.size());
- for (const auto &coordinate: linearRing) {
- mbglLinearRing.emplace_back(asMapboxGLPoint(coordinate));
- }
- mbglPolygon.emplace_back(std::move(mbglLinearRing));
- }
- return mbglPolygon;
-};
-
-mbgl::MultiPolygon<double> asMapboxGLMultiPolygon(const QMapbox::CoordinatesCollections &multiPolygon) {
- mbgl::MultiPolygon<double> mbglMultiPolygon;
- mbglMultiPolygon.reserve(multiPolygon.size());
- for (const auto &polygon : multiPolygon) {
- mbglMultiPolygon.emplace_back(std::forward<mbgl::Polygon<double>>(asMapboxGLPolygon(polygon)));
- }
- return mbglMultiPolygon;
-};
-
-mbgl::Value asMapboxGLPropertyValue(const QVariant &value) {
- auto valueList = [](const QVariantList &list) {
- std::vector<mbgl::Value> mbglList;
- mbglList.reserve(list.size());
- for (const auto& listValue : list) {
- mbglList.emplace_back(asMapboxGLPropertyValue(listValue));
- }
- return mbglList;
- };
-
- auto valueMap = [](const QVariantMap &map) {
- std::unordered_map<std::string, mbgl::Value> mbglMap;
- mbglMap.reserve(map.size());
- auto it = map.constBegin();
- while (it != map.constEnd()) {
- mbglMap.emplace(std::make_pair(it.key().toStdString(), asMapboxGLPropertyValue(it.value())));
- ++it;
- }
- return mbglMap;
- };
-
- switch (value.type()) {
-#if QT_VERSION >= 0x050000
- case QMetaType::UnknownType:
-#else
- case QVariant::Invalid:
-#endif
- return mbgl::NullValue {};
- case QMetaType::Bool:
- return { value.toBool() };
- case QMetaType::ULongLong:
- return { uint64_t(value.toULongLong()) };
- case QMetaType::LongLong:
- return { int64_t(value.toLongLong()) };
- case QMetaType::Double:
- return { value.toDouble() };
- case QMetaType::QString:
- return { value.toString().toStdString() };
- case QMetaType::QVariantList:
- return valueList(value.toList());
- case QMetaType::QVariantMap:
- return valueMap(value.toMap());
- default:
- qWarning() << "Unsupported feature property value:" << value;
- return {};
- }
-}
-
-mbgl::FeatureIdentifier asMapboxGLFeatureIdentifier(const QVariant &id) {
- switch (id.type()) {
-#if QT_VERSION >= 0x050000
- case QMetaType::UnknownType:
-#else
- case QVariant::Invalid:
-#endif
- return {};
- case QMetaType::ULongLong:
- return { uint64_t(id.toULongLong()) };
- case QMetaType::LongLong:
- return { int64_t(id.toLongLong()) };
- case QMetaType::Double:
- return { id.toDouble() };
- case QMetaType::QString:
- return { id.toString().toStdString() };
- default:
- qWarning() << "Unsupported feature identifier:" << id;
- return {};
- }
-}
-
-mbgl::Feature asMapboxGLFeature(const QMapbox::Feature &feature) {
- mbgl::PropertyMap properties;
- properties.reserve(feature.properties.size());
- auto it = feature.properties.constBegin();
- while (it != feature.properties.constEnd()) {
- properties.emplace(std::make_pair(it.key().toStdString(), asMapboxGLPropertyValue(it.value())));
- }
-
- mbgl::FeatureIdentifier id = asMapboxGLFeatureIdentifier(feature.id);
-
- if (feature.type == QMapbox::Feature::PointType) {
- const QMapbox::Coordinates &points = feature.geometry.first().first();
- if (points.size() == 1) {
- return { asMapboxGLPoint(points.first()), std::move(properties), std::move(id) };
- } else {
- return { asMapboxGLMultiPoint(points), std::move(properties), std::move(id) };
- }
- } else if (feature.type == QMapbox::Feature::LineStringType) {
- const QMapbox::CoordinatesCollection &lineStrings = feature.geometry.first();
- if (lineStrings.size() == 1) {
- return { asMapboxGLLineString(lineStrings.first()), std::move(properties), std::move(id) };
- } else {
- return { asMapboxGLMultiLineString(lineStrings), std::move(properties), std::move(id) };
- }
- } else { // PolygonType
- const QMapbox::CoordinatesCollections &polygons = feature.geometry;
- if (polygons.size() == 1) {
- return { asMapboxGLPolygon(polygons.first()), std::move(properties), std::move(id) };
- } else {
- return { asMapboxGLMultiPolygon(polygons), std::move(properties), std::move(id) };
- }
- }
-};
+mbgl::Point<double> asMapboxGLPoint(const QMapbox::Coordinate &coordinate);
+mbgl::MultiPoint<double> asMapboxGLMultiPoint(const QMapbox::Coordinates &multiPoint);
+mbgl::LineString<double> asMapboxGLLineString(const QMapbox::Coordinates &lineString);
+mbgl::MultiLineString<double> asMapboxGLMultiLineString(const QMapbox::CoordinatesCollection &multiLineString);
+mbgl::Polygon<double> asMapboxGLPolygon(const QMapbox::CoordinatesCollection &polygon);
+mbgl::MultiPolygon<double> asMapboxGLMultiPolygon(const QMapbox::CoordinatesCollections &multiPolygon);
+mbgl::Value asMapboxGLPropertyValue(const QVariant &value);
+mbgl::FeatureIdentifier asMapboxGLFeatureIdentifier(const QVariant &id);
+mbgl::Feature asMapboxGLFeature(const QMapbox::Feature &feature);
} // namespace QMapbox
-
-namespace mbgl {
-namespace style {
-namespace conversion {
-
-template <>
-optional<GeoJSON> Converter<GeoJSON>::operator()(const QVariant& value, Error& error) const {
-#if QT_VERSION >= 0x050000
- if (value.typeName() == QStringLiteral("QMapbox::Feature")) {
-#else
- if (value.typeName() == QString("QMapbox::Feature")) {
-#endif
- return GeoJSON { asMapboxGLFeature(value.value<QMapbox::Feature>()) };
- } else if (value.type() != QVariant::ByteArray) {
- error = { "JSON data must be in QByteArray" };
- return {};
- }
-
- QByteArray data = value.toByteArray();
- return convert<GeoJSON>(std::string(data.constData(), data.size()), error);
-}
-
-} // namespace conversion
-} // namespace style
-} // namespace mbgl
diff --git a/platform/qt/src/sqlite3.cpp b/platform/qt/src/sqlite3.cpp
index 7d47ae552b..1d18946364 100644
--- a/platform/qt/src/sqlite3.cpp
+++ b/platform/qt/src/sqlite3.cpp
@@ -6,6 +6,7 @@
#include <QStringList>
#include <QThread>
#include <QVariant>
+#include <QAtomicInt>
#include <cassert>
#include <cstring>
@@ -60,20 +61,26 @@ void checkDatabaseOpenError(const QSqlDatabase &db) {
}
}
+namespace {
+ QString incrementCounter() {
+ static QAtomicInt count = 0;
+ return QString::number(count.fetchAndAddAcquire(1));
+ }
+}
+
class DatabaseImpl {
public:
- DatabaseImpl(const char* filename, int flags) {
- static uint64_t count = 0;
- const QString connectionName = QString::number(uint64_t(QThread::currentThread())) + QString::number(count++);
-
+ DatabaseImpl(const char* filename, int flags)
+ : connectionName(QString::number(uint64_t(QThread::currentThread())) + incrementCounter())
+ {
if (!QSqlDatabase::drivers().contains("QSQLITE")) {
throw Exception { Exception::Code::CANTOPEN, "SQLite driver not found." };
}
assert(!QSqlDatabase::contains(connectionName));
- db.reset(new QSqlDatabase(QSqlDatabase::addDatabase("QSQLITE", connectionName)));
+ auto db = QSqlDatabase::addDatabase("QSQLITE", connectionName);
- QString connectOptions = db->connectOptions();
+ QString connectOptions = db.connectOptions();
if (flags & OpenFlag::ReadOnly) {
if (!connectOptions.isEmpty()) connectOptions.append(';');
connectOptions.append("QSQLITE_OPEN_READONLY");
@@ -83,26 +90,26 @@ public:
connectOptions.append("QSQLITE_ENABLE_SHARED_CACHE");
}
- db->setConnectOptions(connectOptions);
- db->setDatabaseName(QString(filename));
+ db.setConnectOptions(connectOptions);
+ db.setDatabaseName(QString(filename));
- if (!db->open()) {
- checkDatabaseOpenError(*db);
+ if (!db.open()) {
+ checkDatabaseOpenError(db);
}
}
~DatabaseImpl() {
- db->close();
- checkDatabaseError(*db);
+ auto db = QSqlDatabase::database(connectionName);
+ db.close();
+ checkDatabaseError(db);
}
- QScopedPointer<QSqlDatabase> db;
+ QString connectionName;
};
class StatementImpl {
public:
StatementImpl(const QString& sql, const QSqlDatabase& db) : query(db) {
- query.setForwardOnly(true);
if (!query.prepare(sql)) {
checkQueryError(query);
}
@@ -147,17 +154,18 @@ void Database::setBusyTimeout(std::chrono::milliseconds timeout) {
// internally to int, so we need to make sure the limits apply.
std::string timeoutStr = mbgl::util::toString(timeout.count() & INT_MAX);
- QString connectOptions = impl->db->connectOptions();
+ auto db = QSqlDatabase::database(impl->connectionName);
+ QString connectOptions = db.connectOptions();
if (connectOptions.isEmpty()) {
if (!connectOptions.isEmpty()) connectOptions.append(';');
connectOptions.append("QSQLITE_BUSY_TIMEOUT=").append(QString::fromStdString(timeoutStr));
}
- if (impl->db->isOpen()) {
- impl->db->close();
+ if (db.isOpen()) {
+ db.close();
}
- impl->db->setConnectOptions(connectOptions);
- if (!impl->db->open()) {
- checkDatabaseOpenError(*impl->db);
+ db.setConnectOptions(connectOptions);
+ if (!db.open()) {
+ checkDatabaseOpenError(db);
}
}
@@ -169,9 +177,9 @@ void Database::exec(const std::string &sql) {
if (!statement.endsWith(';')) {
statement.append(';');
}
- QSqlQuery query(*impl->db);
- query.setForwardOnly(true);
+ QSqlQuery query(QSqlDatabase::database(impl->connectionName));
query.prepare(statement);
+
if (!query.exec()) {
checkQueryError(query);
}
@@ -183,7 +191,7 @@ Statement Database::prepare(const char *query) {
}
Statement::Statement(Database *db, const char *sql)
- : impl(std::make_unique<StatementImpl>(QString(sql), *db->impl->db)) {
+ : impl(std::make_unique<StatementImpl>(QString(sql), QSqlDatabase::database(db->impl->connectionName))) {
assert(impl);
}
@@ -296,20 +304,20 @@ void Statement::bindBlob(int offset, const std::vector<uint8_t>& value, bool ret
bool Statement::run() {
assert(impl);
- if (impl->query.isValid()) {
- return impl->query.next();
- }
- assert(!impl->query.isActive());
- impl->query.setForwardOnly(true);
- if (!impl->query.exec()) {
- checkQueryError(impl->query);
+ if (!impl->query.isValid()) {
+ if (impl->query.exec()) {
+ impl->lastInsertRowId = impl->query.lastInsertId().value<int64_t>();
+ impl->changes = impl->query.numRowsAffected();
+ } else {
+ checkQueryError(impl->query);
+ }
}
- impl->lastInsertRowId = impl->query.lastInsertId().value<int64_t>();
- impl->changes = impl->query.numRowsAffected();
+ const bool hasNext = impl->query.next();
+ if (!hasNext) impl->query.finish();
- return impl->query.next();
+ return hasNext;
}
template bool Statement::get(int);
diff --git a/platform/qt/test/qmapboxgl.test.cpp b/platform/qt/test/qmapboxgl.test.cpp
index c6ae3ed403..932460b932 100644
--- a/platform/qt/test/qmapboxgl.test.cpp
+++ b/platform/qt/test/qmapboxgl.test.cpp
@@ -7,6 +7,9 @@
// We're using QGLFramebufferObject, which is only available in Qt 5 and up.
#if QT_VERSION >= 0x050000
+#include <QOpenGLContext>
+#include <QOpenGLFunctions>
+
QMapboxGLTest::QMapboxGLTest() : size(512, 512), fbo((assert(widget.context()->isValid()), widget.makeCurrent(), size)), map(nullptr, settings, size) {
connect(&map, SIGNAL(mapChanged(QMapboxGL::MapChange)),
this, SLOT(onMapChanged(QMapboxGL::MapChange)));
@@ -37,7 +40,7 @@ void QMapboxGLTest::onMapChanged(QMapboxGL::MapChange change) {
void QMapboxGLTest::onNeedsRendering() {
widget.makeCurrent();
fbo.bind();
- glViewport(0, 0, fbo.width(), fbo.height());
+ QOpenGLContext::currentContext()->functions()->glViewport(0, 0, fbo.width(), fbo.height());
map.render();
}
diff --git a/scripts/generate-style-code.js b/scripts/generate-style-code.js
index fe9a1a906b..443f0c7cc9 100644
--- a/scripts/generate-style-code.js
+++ b/scripts/generate-style-code.js
@@ -186,8 +186,8 @@ for (const layer of layers) {
writeIfModified(`src/mbgl/style/layers/${layerFileName}_layer_properties.cpp`, propertiesCpp(layer));
}
-const propertySettersHpp = ejs.compile(fs.readFileSync('include/mbgl/style/conversion/make_property_setters.hpp.ejs', 'utf8'), {strict: true});
-writeIfModified('include/mbgl/style/conversion/make_property_setters.hpp', propertySettersHpp({layers: layers}));
+const propertySettersHpp = ejs.compile(fs.readFileSync('src/mbgl/style/conversion/make_property_setters.hpp.ejs', 'utf8'), {strict: true});
+writeIfModified('src/mbgl/style/conversion/make_property_setters.hpp', propertySettersHpp({layers: layers}));
// Light
const lightProperties = Object.keys(spec[`light`]).reduce((memo, name) => {
diff --git a/src/mbgl/annotation/render_annotation_source.hpp b/src/mbgl/annotation/render_annotation_source.hpp
index 9536b2e101..6209c943f1 100644
--- a/src/mbgl/annotation/render_annotation_source.hpp
+++ b/src/mbgl/annotation/render_annotation_source.hpp
@@ -43,7 +43,7 @@ private:
template <>
inline bool RenderSource::is<RenderAnnotationSource>() const {
- return baseImpl->type == SourceType::Annotations;
+ return baseImpl->type == style::SourceType::Annotations;
}
} // namespace mbgl
diff --git a/src/mbgl/gl/context.cpp b/src/mbgl/gl/context.cpp
index 22b446e6d0..a4f9cead0e 100644
--- a/src/mbgl/gl/context.cpp
+++ b/src/mbgl/gl/context.cpp
@@ -596,19 +596,19 @@ void Context::clear(optional<mbgl::Color> color,
if (color) {
mask |= GL_COLOR_BUFFER_BIT;
clearColor = *color;
- colorMask = { true, true, true, true };
+ colorMask = value::ColorMask::Default;
}
if (depth) {
mask |= GL_DEPTH_BUFFER_BIT;
clearDepth = *depth;
- depthMask = true;
+ depthMask = value::DepthMask::Default;
}
if (stencil) {
mask |= GL_STENCIL_BUFFER_BIT;
clearStencil = *stencil;
- stencilMask = 0xFF;
+ stencilMask = value::StencilMask::Default;
}
MBGL_CHECK_ERROR(glClear(mask));
diff --git a/src/mbgl/gl/gl.hpp b/src/mbgl/gl/gl.hpp
index 3e21731330..976b7d2f74 100644
--- a/src/mbgl/gl/gl.hpp
+++ b/src/mbgl/gl/gl.hpp
@@ -1,36 +1,10 @@
#pragma once
+#include <mbgl/gl/gl_impl.hpp>
+
#include <stdexcept>
#include <limits>
-#if __APPLE__
- #include "TargetConditionals.h"
- #if TARGET_OS_IPHONE
- #include <OpenGLES/ES2/gl.h>
- #include <OpenGLES/ES2/glext.h>
- #elif TARGET_IPHONE_SIMULATOR
- #include <OpenGLES/ES2/gl.h>
- #include <OpenGLES/ES2/glext.h>
- #elif TARGET_OS_MAC
- #include <OpenGL/OpenGL.h>
- #include <OpenGL/gl.h>
- #include <OpenGL/glext.h>
- #else
- #error Unsupported Apple platform
- #endif
-#elif __ANDROID__ || MBGL_USE_GLES2
- #define GL_GLEXT_PROTOTYPES
- #include <GLES2/gl2.h>
- #include <GLES2/gl2ext.h>
-#elif __QT__ && QT_VERSION >= 0x050000
- #define GL_GLEXT_PROTOTYPES
- #include <QtGui/qopengl.h>
-#else
- #define GL_GLEXT_PROTOTYPES
- #include <GL/gl.h>
- #include <GL/glext.h>
-#endif
-
namespace mbgl {
namespace gl {
diff --git a/src/mbgl/map/map.cpp b/src/mbgl/map/map.cpp
index 6eb555ad1e..378bd40ab7 100644
--- a/src/mbgl/map/map.cpp
+++ b/src/mbgl/map/map.cpp
@@ -617,6 +617,35 @@ ViewportMode Map::getViewportMode() const {
return impl->transform.getViewportMode();
}
+#pragma mark - Projection mode
+
+void Map::setAxonometric(bool axonometric) {
+ impl->transform.setAxonometric(axonometric);
+ impl->onUpdate();
+}
+
+bool Map::getAxonometric() const {
+ return impl->transform.getAxonometric();
+}
+
+void Map::setXSkew(double xSkew) {
+ impl->transform.setXSkew(xSkew);
+ impl->onUpdate();
+}
+
+double Map::getXSkew() const {
+ return impl->transform.getXSkew();
+}
+
+void Map::setYSkew(double ySkew) {
+ impl->transform.setYSkew(ySkew);
+ impl->onUpdate();
+}
+
+double Map::getYSkew() const {
+ return impl->transform.getYSkew();
+}
+
#pragma mark - Projection
ScreenCoordinate Map::pixelForLatLng(const LatLng& latLng) const {
diff --git a/src/mbgl/map/transform.cpp b/src/mbgl/map/transform.cpp
index 2bb25af28f..105adf0400 100644
--- a/src/mbgl/map/transform.cpp
+++ b/src/mbgl/map/transform.cpp
@@ -527,6 +527,32 @@ ViewportMode Transform::getViewportMode() const {
return state.getViewportMode();
}
+#pragma mark - Projection mode
+
+void Transform::setAxonometric(bool axonometric) {
+ state.axonometric = axonometric;
+}
+
+bool Transform::getAxonometric() const {
+ return state.axonometric;
+}
+
+void Transform::setXSkew(double xSkew) {
+ state.xSkew = xSkew;
+}
+
+double Transform::getXSkew() const {
+ return state.xSkew;
+}
+
+void Transform::setYSkew(double ySkew) {
+ state.ySkew = ySkew;
+}
+
+double Transform::getYSkew() const {
+ return state.ySkew;
+}
+
#pragma mark - Transition
void Transform::startTransition(const CameraOptions& camera,
diff --git a/src/mbgl/map/transform.hpp b/src/mbgl/map/transform.hpp
index 749228bdf5..d429c57661 100644
--- a/src/mbgl/map/transform.hpp
+++ b/src/mbgl/map/transform.hpp
@@ -125,6 +125,14 @@ public:
void setViewportMode(ViewportMode);
ViewportMode getViewportMode() const;
+ // Projection mode
+ void setAxonometric(bool);
+ bool getAxonometric() const;
+ void setXSkew(double xSkew);
+ double getXSkew() const;
+ void setYSkew(double ySkew);
+ double getYSkew() const;
+
// Transitions
bool inTransition() const;
void updateTransitions(const TimePoint& now);
diff --git a/src/mbgl/map/transform_state.cpp b/src/mbgl/map/transform_state.cpp
index d1a320beae..d79a65c61e 100644
--- a/src/mbgl/map/transform_state.cpp
+++ b/src/mbgl/map/transform_state.cpp
@@ -66,6 +66,15 @@ void TransformState::getProjMatrix(mat4& projMatrix, uint16_t nearZ) const {
matrix::translate(projMatrix, projMatrix, pixel_x() - size.width / 2.0f,
pixel_y() - size.height / 2.0f, 0);
+ if (axonometric) {
+ // mat[11] controls perspective
+ projMatrix[11] = 0;
+
+ // mat[8], mat[9] control x-skew, y-skew
+ projMatrix[8] = xSkew;
+ projMatrix[9] = ySkew;
+ }
+
matrix::scale(projMatrix, projMatrix, 1, 1,
1.0 / Projection::getMetersPerPixelAtLatitude(getLatLng(LatLng::Unwrapped).latitude(), getZoom()));
}
diff --git a/src/mbgl/map/transform_state.hpp b/src/mbgl/map/transform_state.hpp
index 59522d89fd..0dd6d5a15e 100644
--- a/src/mbgl/map/transform_state.hpp
+++ b/src/mbgl/map/transform_state.hpp
@@ -134,6 +134,9 @@ private:
// `fov = 2 * arctan((height / 2) / (height * 1.5))`
double fov = 0.6435011087932844;
double pitch = 0.0;
+ double xSkew = 0.0;
+ double ySkew = 1.0;
+ bool axonometric = false;
// cache values for spherical mercator math
double Bc = Projection::worldSize(scale) / util::DEGREES_MAX;
diff --git a/src/mbgl/programs/symbol_program.hpp b/src/mbgl/programs/symbol_program.hpp
index a7abf94f56..5065b364f7 100644
--- a/src/mbgl/programs/symbol_program.hpp
+++ b/src/mbgl/programs/symbol_program.hpp
@@ -128,23 +128,6 @@ public:
}
};
-// Return the smallest range of stops that covers the interval [lowerZoom, upperZoom]
-template <class Stops>
-Range<float> getCoveringStops(Stops s, float lowerZoom, float upperZoom) {
- assert(!s.stops.empty());
- auto minIt = s.stops.lower_bound(lowerZoom);
- auto maxIt = s.stops.lower_bound(upperZoom);
-
- // lower_bound yields first element >= lowerZoom, but we want the *last*
- // element <= lowerZoom, so if we found a stop > lowerZoom, back up by one.
- if (minIt != s.stops.begin() && minIt != s.stops.end() && minIt->first > lowerZoom) {
- minIt--;
- }
- return Range<float> {
- minIt == s.stops.end() ? s.stops.rbegin()->first : minIt->first,
- maxIt == s.stops.end() ? s.stops.rbegin()->first : maxIt->first
- };
-}
class ConstantSymbolSizeBinder final : public SymbolSizeBinder {
public:
@@ -155,19 +138,12 @@ public:
: layoutSize(defaultValue) {}
ConstantSymbolSizeBinder(const float tileZoom, const style::CameraFunction<float>& function_, const float /*defaultValue*/)
- : layoutSize(function_.evaluate(tileZoom + 1)) {
- function_.stops.match(
- [&] (const style::ExponentialStops<float>& stops) {
- const auto& zoomLevels = getCoveringStops(stops, tileZoom, tileZoom + 1);
- coveringRanges = std::make_tuple(
- zoomLevels,
- Range<float> { function_.evaluate(zoomLevels.min), function_.evaluate(zoomLevels.max) }
- );
- functionInterpolationBase = stops.base;
- },
- [&] (const style::IntervalStops<float>&) {
- function = function_;
- }
+ : layoutSize(function_.evaluate(tileZoom + 1)),
+ function(function_) {
+ const Range<float> zoomLevels = function_.getCoveringStops(tileZoom, tileZoom + 1);
+ coveringRanges = std::make_tuple(
+ zoomLevels,
+ Range<float> { function_.evaluate(zoomLevels.min), function_.evaluate(zoomLevels.max) }
);
}
@@ -185,7 +161,7 @@ public:
const Range<float>& zoomLevels = std::get<0>(*coveringRanges);
const Range<float>& sizeLevels = std::get<1>(*coveringRanges);
float t = util::clamp(
- util::interpolationFactor(*functionInterpolationBase, zoomLevels, currentZoom),
+ function->interpolationFactor(zoomLevels, currentZoom),
0.0f, 1.0f
);
size = sizeLevels.min + t * (sizeLevels.max - sizeLevels.min);
@@ -198,10 +174,7 @@ public:
}
float layoutSize;
- // used for exponential functions
optional<std::tuple<Range<float>, Range<float>>> coveringRanges;
- optional<float> functionInterpolationBase;
- // used for interval functions
optional<style::CameraFunction<float>> function;
};
@@ -226,7 +199,7 @@ public:
return { true, false, unused, unused, unused };
}
- const style::SourceFunction<float>& function;
+ style::SourceFunction<float> function;
const float defaultValue;
};
@@ -237,9 +210,7 @@ public:
: function(function_),
defaultValue(defaultValue_),
layoutZoom(tileZoom + 1),
- coveringZoomStops(function.stops.match(
- [&] (const auto& stops) {
- return getCoveringStops(stops, tileZoom, tileZoom + 1); }))
+ coveringZoomStops(function.getCoveringStops(tileZoom, tileZoom + 1))
{}
Range<float> getVertexSizeData(const GeometryTileFeature& feature) override {
@@ -251,7 +222,7 @@ public:
ZoomEvaluatedSize evaluateForZoom(float currentZoom) const override {
float sizeInterpolationT = util::clamp(
- util::interpolationFactor(1.0f, coveringZoomStops, currentZoom),
+ function.interpolationFactor(coveringZoomStops, currentZoom),
0.0f, 1.0f
);
@@ -259,7 +230,7 @@ public:
return { false, false, sizeInterpolationT, unused, unused };
}
- const style::CompositeFunction<float>& function;
+ style::CompositeFunction<float> function;
const float defaultValue;
float layoutZoom;
Range<float> coveringZoomStops;
diff --git a/src/mbgl/renderer/paint_parameters.hpp b/src/mbgl/renderer/paint_parameters.hpp
index 4a2c2c6f12..60f5af4e9a 100644
--- a/src/mbgl/renderer/paint_parameters.hpp
+++ b/src/mbgl/renderer/paint_parameters.hpp
@@ -2,6 +2,7 @@
#include <mbgl/renderer/render_pass.hpp>
#include <mbgl/renderer/render_light.hpp>
+#include <mbgl/renderer/mode.hpp>
#include <mbgl/map/mode.hpp>
#include <mbgl/gl/depth_mode.hpp>
#include <mbgl/gl/stencil_mode.hpp>
diff --git a/src/mbgl/renderer/paint_property_binder.hpp b/src/mbgl/renderer/paint_property_binder.hpp
index 652948c8df..3a49882f12 100644
--- a/src/mbgl/renderer/paint_property_binder.hpp
+++ b/src/mbgl/renderer/paint_property_binder.hpp
@@ -190,11 +190,11 @@ public:
CompositeFunctionPaintPropertyBinder(style::CompositeFunction<T> function_, float zoom, T defaultValue_)
: function(std::move(function_)),
defaultValue(std::move(defaultValue_)),
- rangeOfCoveringRanges(function.rangeOfCoveringRanges({zoom, zoom + 1})) {
+ zoomRange({zoom, zoom + 1}) {
}
void populateVertexVector(const GeometryTileFeature& feature, std::size_t length) override {
- Range<T> range = function.evaluate(rangeOfCoveringRanges, feature, defaultValue);
+ Range<T> range = function.evaluate(zoomRange, feature, defaultValue);
this->statistics.add(range.min);
this->statistics.add(range.max);
AttributeValue value = zoomInterpolatedAttributeValue(
@@ -219,9 +219,9 @@ public:
float interpolationFactor(float currentZoom) const override {
if (function.useIntegerZoom) {
- return util::interpolationFactor(1.0f, { rangeOfCoveringRanges.min.zoom, rangeOfCoveringRanges.max.zoom }, std::floor(currentZoom));
+ return function.interpolationFactor(zoomRange, std::floor(currentZoom));
} else {
- return util::interpolationFactor(1.0f, { rangeOfCoveringRanges.min.zoom, rangeOfCoveringRanges.max.zoom }, currentZoom);
+ return function.interpolationFactor(zoomRange, currentZoom);
}
}
@@ -237,8 +237,7 @@ public:
private:
style::CompositeFunction<T> function;
T defaultValue;
- using CoveringRanges = typename style::CompositeFunction<T>::CoveringRanges;
- Range<CoveringRanges> rangeOfCoveringRanges;
+ Range<float> zoomRange;
gl::VertexVector<Vertex> vertexVector;
optional<gl::VertexBuffer<Vertex>> vertexBuffer;
};
diff --git a/src/mbgl/renderer/renderer_impl.cpp b/src/mbgl/renderer/renderer_impl.cpp
index 7339756e52..5987e69374 100644
--- a/src/mbgl/renderer/renderer_impl.cpp
+++ b/src/mbgl/renderer/renderer_impl.cpp
@@ -298,7 +298,9 @@ void Renderer::Impl::render(const UpdateParameters& updateParameters) {
if (const RenderBackgroundLayer* background = layer->as<RenderBackgroundLayer>()) {
const BackgroundPaintProperties::PossiblyEvaluated& paint = background->evaluated;
- if (layerImpl.get() == layerImpls->at(0).get() && paint.get<BackgroundPattern>().from.empty()) {
+ if (parameters.contextMode == GLContextMode::Unique
+ && layerImpl.get() == layerImpls->at(0).get()
+ && paint.get<BackgroundPattern>().from.empty()) {
// This is a solid background. We can use glClear().
backgroundColor = paint.get<BackgroundColor>() * paint.get<BackgroundOpacity>();
} else {
@@ -434,13 +436,17 @@ void Renderer::Impl::render(const UpdateParameters& updateParameters) {
// Renders the backdrop of the OpenGL view. This also paints in areas where we don't have any
// tiles whatsoever.
{
+ using namespace gl::value;
+
MBGL_DEBUG_GROUP(parameters.context, "clear");
parameters.backend.bind();
- parameters.context.clear((parameters.debugOptions & MapDebugOptions::Overdraw)
- ? Color::black()
- : backgroundColor,
- 1.0f,
- 0);
+ if (parameters.debugOptions & MapDebugOptions::Overdraw) {
+ parameters.context.clear(Color::black(), ClearDepth::Default, ClearStencil::Default);
+ } else if (parameters.contextMode == GLContextMode::Shared) {
+ parameters.context.clear({}, ClearDepth::Default, ClearStencil::Default);
+ } else {
+ parameters.context.clear(backgroundColor, ClearDepth::Default, ClearStencil::Default);
+ }
}
// - CLIPPING MASKS ----------------------------------------------------------------------------
diff --git a/src/mbgl/renderer/renderer_impl.hpp b/src/mbgl/renderer/renderer_impl.hpp
index db2a6e7a74..720e01ed53 100644
--- a/src/mbgl/renderer/renderer_impl.hpp
+++ b/src/mbgl/renderer/renderer_impl.hpp
@@ -1,5 +1,6 @@
#pragma once
+#include <mbgl/renderer/mode.hpp>
#include <mbgl/renderer/renderer.hpp>
#include <mbgl/renderer/render_source_observer.hpp>
#include <mbgl/renderer/render_light.hpp>
@@ -9,7 +10,6 @@
#include <mbgl/style/layer.hpp>
#include <mbgl/map/transform_state.hpp>
#include <mbgl/map/zoom_history.hpp>
-#include <mbgl/map/mode.hpp>
#include <mbgl/text/glyph_manager_observer.hpp>
#include <memory>
diff --git a/src/mbgl/renderer/sources/render_geojson_source.hpp b/src/mbgl/renderer/sources/render_geojson_source.hpp
index 72ab4879ef..b2e06c68d4 100644
--- a/src/mbgl/renderer/sources/render_geojson_source.hpp
+++ b/src/mbgl/renderer/sources/render_geojson_source.hpp
@@ -48,7 +48,7 @@ private:
template <>
inline bool RenderSource::is<RenderGeoJSONSource>() const {
- return baseImpl->type == SourceType::GeoJSON;
+ return baseImpl->type == style::SourceType::GeoJSON;
}
} // namespace mbgl
diff --git a/src/mbgl/renderer/sources/render_image_source.hpp b/src/mbgl/renderer/sources/render_image_source.hpp
index 7b69d09fa7..8d80838c3b 100644
--- a/src/mbgl/renderer/sources/render_image_source.hpp
+++ b/src/mbgl/renderer/sources/render_image_source.hpp
@@ -52,7 +52,7 @@ private:
template <>
inline bool RenderSource::is<RenderImageSource>() const {
- return baseImpl->type == SourceType::Image;
+ return baseImpl->type == style::SourceType::Image;
}
} // namespace mbgl
diff --git a/src/mbgl/renderer/sources/render_raster_source.hpp b/src/mbgl/renderer/sources/render_raster_source.hpp
index 01de812309..e1bf5798ff 100644
--- a/src/mbgl/renderer/sources/render_raster_source.hpp
+++ b/src/mbgl/renderer/sources/render_raster_source.hpp
@@ -44,7 +44,7 @@ private:
template <>
inline bool RenderSource::is<RenderRasterSource>() const {
- return baseImpl->type == SourceType::Raster;
+ return baseImpl->type == style::SourceType::Raster;
}
} // namespace mbgl
diff --git a/src/mbgl/renderer/sources/render_vector_source.hpp b/src/mbgl/renderer/sources/render_vector_source.hpp
index 5e5c6d1108..ac319a167e 100644
--- a/src/mbgl/renderer/sources/render_vector_source.hpp
+++ b/src/mbgl/renderer/sources/render_vector_source.hpp
@@ -44,7 +44,7 @@ private:
template <>
inline bool RenderSource::is<RenderVectorSource>() const {
- return baseImpl->type == SourceType::Vector;
+ return baseImpl->type == style::SourceType::Vector;
}
} // namespace mbgl
diff --git a/src/mbgl/renderer/tile_pyramid.hpp b/src/mbgl/renderer/tile_pyramid.hpp
index 73a8d34c1c..ac4572b103 100644
--- a/src/mbgl/renderer/tile_pyramid.hpp
+++ b/src/mbgl/renderer/tile_pyramid.hpp
@@ -37,7 +37,7 @@ public:
bool needsRendering,
bool needsRelayout,
const TileParameters&,
- SourceType type,
+ style::SourceType type,
uint16_t tileSize,
Range<uint8_t> zoomRange,
std::function<std::unique_ptr<Tile> (const OverscaledTileID&)> createTile);
diff --git a/src/mbgl/style/conversion/constant.cpp b/src/mbgl/style/conversion/constant.cpp
new file mode 100644
index 0000000000..e837c4e70b
--- /dev/null
+++ b/src/mbgl/style/conversion/constant.cpp
@@ -0,0 +1,94 @@
+#include <mbgl/style/conversion/constant.hpp>
+
+namespace mbgl {
+namespace style {
+namespace conversion {
+
+optional<bool> Converter<bool>::operator()(const Convertible& value, Error& error) const {
+ optional<bool> converted = toBool(value);
+ if (!converted) {
+ error = { "value must be a boolean" };
+ return {};
+ }
+ return *converted;
+}
+
+optional<float> Converter<float>::operator()(const Convertible& value, Error& error) const {
+ optional<float> converted = toNumber(value);
+ if (!converted) {
+ error = { "value must be a number" };
+ return {};
+ }
+ return *converted;
+}
+
+optional<std::string> Converter<std::string>::operator()(const Convertible& value, Error& error) const {
+ optional<std::string> converted = toString(value);
+ if (!converted) {
+ error = { "value must be a string" };
+ return {};
+ }
+ return *converted;
+}
+
+optional<Color> Converter<Color>::operator()(const Convertible& value, Error& error) const {
+ optional<std::string> string = toString(value);
+ if (!string) {
+ error = { "value must be a string" };
+ return {};
+ }
+
+ optional<Color> color = Color::parse(*string);
+ if (!color) {
+ error = { "value must be a valid color" };
+ return {};
+ }
+
+ return *color;
+}
+
+optional<std::vector<float>> Converter<std::vector<float>>::operator()(const Convertible& value, Error& error) const {
+ if (!isArray(value)) {
+ error = { "value must be an array" };
+ return {};
+ }
+
+ std::vector<float> result;
+ result.reserve(arrayLength(value));
+
+ for (std::size_t i = 0; i < arrayLength(value); ++i) {
+ optional<float> number = toNumber(arrayMember(value, i));
+ if (!number) {
+ error = { "value must be an array of numbers" };
+ return {};
+ }
+ result.push_back(*number);
+ }
+
+ return result;
+}
+
+optional<std::vector<std::string>> Converter<std::vector<std::string>>::operator()(const Convertible& value, Error& error) const {
+ if (!isArray(value)) {
+ error = { "value must be an array" };
+ return {};
+ }
+
+ std::vector<std::string> result;
+ result.reserve(arrayLength(value));
+
+ for (std::size_t i = 0; i < arrayLength(value); ++i) {
+ optional<std::string> string = toString(arrayMember(value, i));
+ if (!string) {
+ error = { "value must be an array of strings" };
+ return {};
+ }
+ result.push_back(*string);
+ }
+
+ return result;
+}
+
+} // namespace conversion
+} // namespace style
+} // namespace mbgl
diff --git a/src/mbgl/style/conversion/coordinate.cpp b/src/mbgl/style/conversion/coordinate.cpp
new file mode 100644
index 0000000000..9b2be3381e
--- /dev/null
+++ b/src/mbgl/style/conversion/coordinate.cpp
@@ -0,0 +1,29 @@
+#include <mbgl/style/conversion/coordinate.hpp>
+
+namespace mbgl {
+namespace style {
+namespace conversion {
+
+optional<LatLng> Converter<LatLng>::operator() (const Convertible& value, Error& error) const {
+ if (!isArray(value) || arrayLength(value) < 2 ) {
+ error = { "coordinate array must contain numeric longitude and latitude values" };
+ return {};
+ }
+ //Style spec uses GeoJSON convention for specifying coordinates
+ optional<double> latitude = toDouble(arrayMember(value, 1));
+ optional<double> longitude = toDouble(arrayMember(value, 0));
+
+ if (!latitude || !longitude) {
+ error = { "coordinate array must contain numeric longitude and latitude values" };
+ return {};
+ }
+ if (*latitude < -90 || *latitude > 90 ){
+ error = { "coordinate latitude must be between -90 and 90" };
+ return {};
+ }
+ return LatLng(*latitude, *longitude);
+}
+
+} // namespace conversion
+} // namespace style
+} // namespace mbgl
diff --git a/src/mbgl/style/conversion/filter.cpp b/src/mbgl/style/conversion/filter.cpp
new file mode 100644
index 0000000000..bb7bb6ea98
--- /dev/null
+++ b/src/mbgl/style/conversion/filter.cpp
@@ -0,0 +1,248 @@
+#include <mbgl/style/conversion/filter.hpp>
+#include <mbgl/util/geometry.hpp>
+
+namespace mbgl {
+namespace style {
+namespace conversion {
+
+static optional<Value> normalizeValue(const optional<Value>& value, Error& error) {
+ if (!value) {
+ error = { "filter expression value must be a boolean, number, or string" };
+ return {};
+ } else {
+ return *value;
+ }
+}
+
+static optional<FeatureType> toFeatureType(const Convertible& value, Error& error) {
+ optional<std::string> type = toString(value);
+ if (!type) {
+ error = { "value for $type filter must be a string" };
+ return {};
+ } else if (*type == "Point") {
+ return FeatureType::Point;
+ } else if (*type == "LineString") {
+ return FeatureType::LineString;
+ } else if (*type == "Polygon") {
+ return FeatureType::Polygon;
+ } else {
+ error = { "value for $type filter must be Point, LineString, or Polygon" };
+ return {};
+ }
+}
+
+static optional<FeatureIdentifier> toFeatureIdentifier(const Convertible& value, Error& error) {
+ optional<Value> identifier = toValue(value);
+ if (!identifier) {
+ error = { "filter expression value must be a boolean, number, or string" };
+ return {};
+ } else {
+ return (*identifier).match(
+ [] (uint64_t t) -> optional<FeatureIdentifier> { return { t }; },
+ [] ( int64_t t) -> optional<FeatureIdentifier> { return { t }; },
+ [] ( double t) -> optional<FeatureIdentifier> { return { t }; },
+ [] (const std::string& t) -> optional<FeatureIdentifier> { return { t }; },
+ [&] (const auto&) -> optional<FeatureIdentifier> {
+ error = { "filter expression value must be a boolean, number, or string" };
+ return {};
+ });
+ }
+}
+
+template <class FilterType, class IdentifierFilterType>
+optional<Filter> convertUnaryFilter(const Convertible& value, Error& error) {
+ if (arrayLength(value) < 2) {
+ error = { "filter expression must have 2 elements" };
+ return {};
+ }
+
+ optional<std::string> key = toString(arrayMember(value, 1));
+ if (!key) {
+ error = { "filter expression key must be a string" };
+ return {};
+ }
+
+ if (*key == "$id") {
+ return { IdentifierFilterType {} };
+ } else {
+ return { FilterType { *key } };
+ }
+}
+
+template <class FilterType, class TypeFilterType, class IdentifierFilterType>
+optional<Filter> convertEqualityFilter(const Convertible& value, Error& error) {
+ if (arrayLength(value) < 3) {
+ error = { "filter expression must have 3 elements" };
+ return {};
+ }
+
+ optional<std::string> key = toString(arrayMember(value, 1));
+ if (!key) {
+ error = { "filter expression key must be a string" };
+ return {};
+ }
+
+ if (*key == "$type") {
+ optional<FeatureType> filterValue = toFeatureType(arrayMember(value, 2), error);
+ if (!filterValue) {
+ return {};
+ }
+
+ return { TypeFilterType { *filterValue } };
+
+ } else if (*key == "$id") {
+ optional<FeatureIdentifier> filterValue = toFeatureIdentifier(arrayMember(value, 2), error);
+ if (!filterValue) {
+ return {};
+ }
+
+ return { IdentifierFilterType { *filterValue } };
+
+ } else {
+ optional<Value> filterValue = normalizeValue(toValue(arrayMember(value, 2)), error);
+ if (!filterValue) {
+ return {};
+ }
+
+ return { FilterType { *key, *filterValue } };
+ }
+}
+
+template <class FilterType>
+optional<Filter> convertBinaryFilter(const Convertible& value, Error& error) {
+ if (arrayLength(value) < 3) {
+ error = { "filter expression must have 3 elements" };
+ return {};
+ }
+
+ optional<std::string> key = toString(arrayMember(value, 1));
+ if (!key) {
+ error = { "filter expression key must be a string" };
+ return {};
+ }
+
+ optional<Value> filterValue = normalizeValue(toValue(arrayMember(value, 2)), error);
+ if (!filterValue) {
+ return {};
+ }
+
+ return { FilterType { *key, *filterValue } };
+}
+
+template <class FilterType, class TypeFilterType, class IdentifierFilterType>
+optional<Filter> convertSetFilter(const Convertible& value, Error& error) {
+ if (arrayLength(value) < 2) {
+ error = { "filter expression must at least 2 elements" };
+ return {};
+ }
+
+ optional<std::string> key = toString(arrayMember(value, 1));
+ if (!key) {
+ error = { "filter expression key must be a string" };
+ return {};
+ }
+
+ if (*key == "$type") {
+ std::vector<FeatureType> values;
+ for (std::size_t i = 2; i < arrayLength(value); ++i) {
+ optional<FeatureType> filterValue = toFeatureType(arrayMember(value, i), error);
+ if (!filterValue) {
+ return {};
+ }
+ values.push_back(*filterValue);
+ }
+
+ return { TypeFilterType { std::move(values) } };
+
+ } else if (*key == "$id") {
+ std::vector<FeatureIdentifier> values;
+ for (std::size_t i = 2; i < arrayLength(value); ++i) {
+ optional<FeatureIdentifier> filterValue = toFeatureIdentifier(arrayMember(value, i), error);
+ if (!filterValue) {
+ return {};
+ }
+ values.push_back(*filterValue);
+ }
+
+ return { IdentifierFilterType { std::move(values) } };
+
+ } else {
+ std::vector<Value> values;
+ for (std::size_t i = 2; i < arrayLength(value); ++i) {
+ optional<Value> filterValue = normalizeValue(toValue(arrayMember(value, i)), error);
+ if (!filterValue) {
+ return {};
+ }
+ values.push_back(*filterValue);
+ }
+
+ return { FilterType { *key, std::move(values) } };
+ }
+}
+
+template <class FilterType>
+optional<Filter> convertCompoundFilter(const Convertible& value, Error& error) {
+ std::vector<Filter> filters;
+ for (std::size_t i = 1; i < arrayLength(value); ++i) {
+ optional<Filter> element = convert<Filter>(arrayMember(value, i), error);
+ if (!element) {
+ return {};
+ }
+ filters.push_back(*element);
+ }
+
+ return { FilterType { std::move(filters) } };
+}
+
+optional<Filter> Converter<Filter>::operator()(const Convertible& value, Error& error) const {
+ if (!isArray(value)) {
+ error = { "filter expression must be an array" };
+ return {};
+ }
+
+ if (arrayLength(value) < 1) {
+ error = { "filter expression must have at least 1 element" };
+ return {};
+ }
+
+ optional<std::string> op = toString(arrayMember(value, 0));
+ if (!op) {
+ error = { "filter operator must be a string" };
+ return {};
+ }
+
+ if (*op == "==") {
+ return convertEqualityFilter<EqualsFilter, TypeEqualsFilter, IdentifierEqualsFilter>(value, error);
+ } else if (*op == "!=") {
+ return convertEqualityFilter<NotEqualsFilter, TypeNotEqualsFilter, IdentifierNotEqualsFilter>(value, error);
+ } else if (*op == ">") {
+ return convertBinaryFilter<GreaterThanFilter>(value, error);
+ } else if (*op == ">=") {
+ return convertBinaryFilter<GreaterThanEqualsFilter>(value, error);
+ } else if (*op == "<") {
+ return convertBinaryFilter<LessThanFilter>(value, error);
+ } else if (*op == "<=") {
+ return convertBinaryFilter<LessThanEqualsFilter>(value, error);
+ } else if (*op == "in") {
+ return convertSetFilter<InFilter, TypeInFilter, IdentifierInFilter>(value, error);
+ } else if (*op == "!in") {
+ return convertSetFilter<NotInFilter, TypeNotInFilter, IdentifierNotInFilter>(value, error);
+ } else if (*op == "all") {
+ return convertCompoundFilter<AllFilter>(value, error);
+ } else if (*op == "any") {
+ return convertCompoundFilter<AnyFilter>(value, error);
+ } else if (*op == "none") {
+ return convertCompoundFilter<NoneFilter>(value, error);
+ } else if (*op == "has") {
+ return convertUnaryFilter<HasFilter, HasIdentifierFilter>(value, error);
+ } else if (*op == "!has") {
+ return convertUnaryFilter<NotHasFilter, NotHasIdentifierFilter>(value, error);
+ }
+
+ error = { R"(filter operator must be one of "==", "!=", ">", ">=", "<", "<=", "in", "!in", "all", "any", "none", "has", or "!has")" };
+ return {};
+}
+
+} // namespace conversion
+} // namespace style
+} // namespace mbgl
diff --git a/src/mbgl/style/conversion/geojson.cpp b/src/mbgl/style/conversion/geojson.cpp
index 8103e9014a..e39a1a80eb 100644
--- a/src/mbgl/style/conversion/geojson.cpp
+++ b/src/mbgl/style/conversion/geojson.cpp
@@ -1,26 +1,16 @@
#include <mbgl/style/conversion/geojson.hpp>
#include <mbgl/style/conversion/json.hpp>
-#include <mbgl/util/rapidjson.hpp>
-
-#include <mapbox/geojson.hpp>
-#include <mapbox/geojson/rapidjson.hpp>
namespace mbgl {
namespace style {
namespace conversion {
-optional<GeoJSON> Converter<GeoJSON>::operator()(const std::string& value, Error& error) const {
- return convertJSON<GeoJSON>(value, error);
+optional<GeoJSON> Converter<GeoJSON>::operator()(const Convertible& value, Error& error) const {
+ return toGeoJSON(value, error);
}
-template <>
-optional<GeoJSON> Converter<GeoJSON>::operator()(const JSValue& value, Error& error) const {
- try {
- return mapbox::geojson::convert(value);
- } catch (const std::exception& ex) {
- error = { ex.what() };
- return {};
- }
+optional<GeoJSON> parseGeoJSON(const std::string& value, Error& error) {
+ return convertJSON<GeoJSON>(value, error);
}
} // namespace conversion
diff --git a/src/mbgl/style/conversion/geojson_options.cpp b/src/mbgl/style/conversion/geojson_options.cpp
new file mode 100644
index 0000000000..a2c5ed8816
--- /dev/null
+++ b/src/mbgl/style/conversion/geojson_options.cpp
@@ -0,0 +1,85 @@
+#include <mbgl/style/conversion/geojson_options.hpp>
+
+namespace mbgl {
+namespace style {
+namespace conversion {
+
+optional<GeoJSONOptions> Converter<GeoJSONOptions>::operator()(const Convertible& value, Error& error) const {
+ GeoJSONOptions options;
+
+ const auto minzoomValue = objectMember(value, "minzoom");
+ if (minzoomValue) {
+ if (toNumber(*minzoomValue)) {
+ options.minzoom = static_cast<uint8_t>(*toNumber(*minzoomValue));
+ } else {
+ error = { "GeoJSON source minzoom value must be a number" };
+ return {};
+ }
+ }
+
+ const auto maxzoomValue = objectMember(value, "maxzoom");
+ if (maxzoomValue) {
+ if (toNumber(*maxzoomValue)) {
+ options.maxzoom = static_cast<uint8_t>(*toNumber(*maxzoomValue));
+ } else {
+ error = { "GeoJSON source maxzoom value must be a number" };
+ return {};
+ }
+ }
+
+ const auto bufferValue = objectMember(value, "buffer");
+ if (bufferValue) {
+ if (toNumber(*bufferValue)) {
+ options.buffer = static_cast<uint16_t>(*toNumber(*bufferValue));
+ } else {
+ error = { "GeoJSON source buffer value must be a number" };
+ return {};
+ }
+ }
+
+ const auto toleranceValue = objectMember(value, "tolerance");
+ if (toleranceValue) {
+ if (toNumber(*toleranceValue)) {
+ options.tolerance = static_cast<double>(*toNumber(*toleranceValue));
+ } else {
+ error = { "GeoJSON source tolerance value must be a number" };
+ return {};
+ }
+ }
+
+ const auto clusterValue = objectMember(value, "cluster");
+ if (clusterValue) {
+ if (toBool(*clusterValue)) {
+ options.cluster = *toBool(*clusterValue);
+ } else {
+ error = { "GeoJSON source cluster value must be a boolean" };
+ return {};
+ }
+ }
+
+ const auto clusterMaxZoomValue = objectMember(value, "clusterMaxZoom");
+ if (clusterMaxZoomValue) {
+ if (toNumber(*clusterMaxZoomValue)) {
+ options.clusterMaxZoom = static_cast<uint8_t>(*toNumber(*clusterMaxZoomValue));
+ } else {
+ error = { "GeoJSON source clusterMaxZoom value must be a number" };
+ return {};
+ }
+ }
+
+ const auto clusterRadiusValue = objectMember(value, "clusterRadius");
+ if (clusterRadiusValue) {
+ if (toNumber(*clusterRadiusValue)) {
+ options.clusterRadius = static_cast<double>(*toNumber(*clusterRadiusValue));
+ } else {
+ error = { "GeoJSON source clusterRadius value must be a number" };
+ return {};
+ }
+ }
+
+ return { options };
+}
+
+} // namespace conversion
+} // namespace style
+} // namespace mbgl
diff --git a/src/mbgl/style/conversion/get_json_type.cpp b/src/mbgl/style/conversion/get_json_type.cpp
new file mode 100644
index 0000000000..cd3b4608b1
--- /dev/null
+++ b/src/mbgl/style/conversion/get_json_type.cpp
@@ -0,0 +1,34 @@
+#include <mbgl/style/conversion/get_json_type.hpp>
+#include <mbgl/util/feature.hpp>
+
+namespace mbgl {
+namespace style {
+namespace conversion {
+
+std::string getJSONType(const Convertible& value) {
+ if (isUndefined(value)) {
+ return "null";
+ }
+ if (isArray(value)) {
+ return "array";
+ }
+ if (isObject(value)) {
+ return "object";
+ }
+ optional<mbgl::Value> v = toValue(value);
+
+ // Since we've already checked the non-atomic types above, value must then
+ // be a string, number, or boolean -- thus, assume that the toValue()
+ // conversion succeeds.
+ assert(v);
+
+ return v->match(
+ [&] (const std::string&) { return "string"; },
+ [&] (bool) { return "boolean"; },
+ [&] (auto) { return "number"; }
+ );
+}
+
+} // namespace conversion
+} // namespace style
+} // namespace mbgl
diff --git a/src/mbgl/style/conversion/json.hpp b/src/mbgl/style/conversion/json.hpp
index 0817ac09df..7dd2378f6b 100644
--- a/src/mbgl/style/conversion/json.hpp
+++ b/src/mbgl/style/conversion/json.hpp
@@ -20,7 +20,7 @@ optional<T> convertJSON(const std::string& json, Error& error, Args&&...args) {
return {};
}
- return convert<T, JSValue>(document, error, std::forward<Args>(args)...);
+ return convert<T>(document, error, std::forward<Args>(args)...);
}
} // namespace conversion
diff --git a/src/mbgl/style/conversion/layer.cpp b/src/mbgl/style/conversion/layer.cpp
new file mode 100644
index 0000000000..0ca582f8dc
--- /dev/null
+++ b/src/mbgl/style/conversion/layer.cpp
@@ -0,0 +1,206 @@
+#include <mbgl/style/conversion/layer.hpp>
+#include <mbgl/style/conversion/constant.hpp>
+#include <mbgl/style/conversion/filter.hpp>
+#include <mbgl/style/conversion/make_property_setters.hpp>
+#include <mbgl/style/layers/background_layer.hpp>
+#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/line_layer.hpp>
+#include <mbgl/style/layers/raster_layer.hpp>
+#include <mbgl/style/layers/symbol_layer.hpp>
+
+namespace mbgl {
+namespace style {
+namespace conversion {
+
+optional<Error> setLayoutProperty(Layer& layer, const std::string& name, const Convertible& value) {
+ static const auto setters = makeLayoutPropertySetters();
+ auto it = setters.find(name);
+ if (it == setters.end()) {
+ return Error { "property not found" };
+ }
+ return it->second(layer, value);
+}
+
+optional<Error> setPaintProperty(Layer& layer, const std::string& name, const Convertible& value) {
+ static const auto setters = makePaintPropertySetters();
+ auto it = setters.find(name);
+ if (it == setters.end()) {
+ return Error { "property not found" };
+ }
+ return it->second(layer, value);
+}
+
+optional<Error> setPaintProperties(Layer& layer, const Convertible& value) {
+ auto paintValue = objectMember(value, "paint");
+ if (!paintValue) {
+ return {};
+ }
+ return eachMember(*paintValue, [&] (const std::string& k, const Convertible& v) {
+ return setPaintProperty(layer, k, v);
+ });
+}
+
+template <class LayerType>
+optional<std::unique_ptr<Layer>> convertVectorLayer(const std::string& id, const Convertible& value, Error& error) {
+ auto sourceValue = objectMember(value, "source");
+ if (!sourceValue) {
+ error = { "layer must have a source" };
+ return {};
+ }
+
+ optional<std::string> source = toString(*sourceValue);
+ if (!source) {
+ error = { "layer source must be a string" };
+ return {};
+ }
+
+ std::unique_ptr<LayerType> layer = std::make_unique<LayerType>(id, *source);
+
+ auto sourceLayerValue = objectMember(value, "source-layer");
+ if (sourceLayerValue) {
+ optional<std::string> sourceLayer = toString(*sourceLayerValue);
+ if (!sourceLayer) {
+ error = { "layer source-layer must be a string" };
+ return {};
+ }
+ layer->setSourceLayer(*sourceLayer);
+ }
+
+ auto filterValue = objectMember(value, "filter");
+ if (filterValue) {
+ optional<Filter> filter = convert<Filter>(*filterValue, error);
+ if (!filter) {
+ return {};
+ }
+ layer->setFilter(*filter);
+ }
+
+ return { std::move(layer) };
+}
+
+static optional<std::unique_ptr<Layer>> convertRasterLayer(const std::string& id, const Convertible& value, Error& error) {
+ auto sourceValue = objectMember(value, "source");
+ if (!sourceValue) {
+ error = { "layer must have a source" };
+ return {};
+ }
+
+ optional<std::string> source = toString(*sourceValue);
+ if (!source) {
+ error = { "layer source must be a string" };
+ return {};
+ }
+
+ return { std::make_unique<RasterLayer>(id, *source) };
+}
+
+static optional<std::unique_ptr<Layer>> convertBackgroundLayer(const std::string& id, const Convertible&, Error&) {
+ return { std::make_unique<BackgroundLayer>(id) };
+}
+
+optional<std::unique_ptr<Layer>> Converter<std::unique_ptr<Layer>>::operator()(const Convertible& value, Error& error) const {
+ if (!isObject(value)) {
+ error = { "layer must be an object" };
+ return {};
+ }
+
+ auto idValue = objectMember(value, "id");
+ if (!idValue) {
+ error = { "layer must have an id" };
+ return {};
+ }
+
+ optional<std::string> id = toString(*idValue);
+ if (!id) {
+ error = { "layer id must be a string" };
+ return {};
+ }
+
+ auto typeValue = objectMember(value, "type");
+ if (!typeValue) {
+ error = { "layer must have a type" };
+ return {};
+ }
+
+ optional<std::string> type = toString(*typeValue);
+ if (!type) {
+ error = { "layer type must be a string" };
+ return {};
+ }
+
+ optional<std::unique_ptr<Layer>> converted;
+
+ if (*type == "fill") {
+ converted = convertVectorLayer<FillLayer>(*id, value, error);
+ } else if (*type == "fill-extrusion") {
+ converted = convertVectorLayer<FillExtrusionLayer>(*id, value, error);
+ } else if (*type == "line") {
+ converted = convertVectorLayer<LineLayer>(*id, value, error);
+ } else if (*type == "circle") {
+ converted = convertVectorLayer<CircleLayer>(*id, value, error);
+ } else if (*type == "symbol") {
+ converted = convertVectorLayer<SymbolLayer>(*id, value, error);
+ } else if (*type == "raster") {
+ converted = convertRasterLayer(*id, value, error);
+ } else if (*type == "background") {
+ converted = convertBackgroundLayer(*id, value, error);
+ } else {
+ error = { "invalid layer type" };
+ return {};
+ }
+
+ if (!converted) {
+ return converted;
+ }
+
+ std::unique_ptr<Layer> layer = std::move(*converted);
+
+ auto minzoomValue = objectMember(value, "minzoom");
+ if (minzoomValue) {
+ optional<float> minzoom = toNumber(*minzoomValue);
+ if (!minzoom) {
+ error = { "minzoom must be numeric" };
+ return {};
+ }
+ layer->setMinZoom(*minzoom);
+ }
+
+ auto maxzoomValue = objectMember(value, "maxzoom");
+ if (maxzoomValue) {
+ optional<float> maxzoom = toNumber(*maxzoomValue);
+ if (!maxzoom) {
+ error = { "maxzoom must be numeric" };
+ return {};
+ }
+ layer->setMaxZoom(*maxzoom);
+ }
+
+ auto layoutValue = objectMember(value, "layout");
+ if (layoutValue) {
+ if (!isObject(*layoutValue)) {
+ error = { "layout must be an object" };
+ return {};
+ }
+ optional<Error> error_ = eachMember(*layoutValue, [&] (const std::string& k, const Convertible& v) {
+ return setLayoutProperty(*layer, k, v);
+ });
+ if (error_) {
+ error = *error_;
+ return {};
+ }
+ }
+
+ optional<Error> error_ = setPaintProperties(*layer, value);
+ if (error_) {
+ error = *error_;
+ return {};
+ }
+
+ return std::move(layer);
+}
+
+} // namespace conversion
+} // namespace style
+} // namespace mbgl
diff --git a/src/mbgl/style/conversion/light.cpp b/src/mbgl/style/conversion/light.cpp
new file mode 100644
index 0000000000..f521f74386
--- /dev/null
+++ b/src/mbgl/style/conversion/light.cpp
@@ -0,0 +1,115 @@
+#include <mbgl/style/conversion/light.hpp>
+#include <mbgl/style/conversion/position.hpp>
+#include <mbgl/style/conversion/property_value.hpp>
+#include <mbgl/style/conversion/transition_options.hpp>
+
+namespace mbgl {
+namespace style {
+namespace conversion {
+
+optional<Light> Converter<Light>::operator()(const Convertible& value, Error& error) const {
+ if (!isObject(value)) {
+ error = { "light must be an object" };
+ return {};
+ }
+
+ Light light;
+
+ const auto anchor = objectMember(value, "anchor");
+ if (anchor) {
+ optional<PropertyValue<LightAnchorType>> convertedAnchor =
+ convert<PropertyValue<LightAnchorType>>(*anchor, error);
+
+ if (convertedAnchor) {
+ light.setAnchor(*convertedAnchor);
+ } else {
+ return {};
+ }
+ }
+
+ const auto anchorTransition = objectMember(value, "anchor-transition");
+ if (anchorTransition) {
+ optional<TransitionOptions> transition =
+ convert<TransitionOptions>(*anchorTransition, error);
+ if (transition) {
+ light.setAnchorTransition(*transition);
+ } else {
+ return {};
+ }
+ }
+
+ const auto color = objectMember(value, "color");
+ if (color) {
+ optional<PropertyValue<Color>> convertedColor =
+ convert<PropertyValue<Color>>(*color, error);
+
+ if (convertedColor) {
+ light.setColor(*convertedColor);
+ } else {
+ return {};
+ }
+ }
+
+ const auto colorTransition = objectMember(value, "color-transition");
+ if (colorTransition) {
+ optional<TransitionOptions> transition =
+ convert<TransitionOptions>(*colorTransition, error);
+ if (transition) {
+ light.setColorTransition(*transition);
+ } else {
+ return {};
+ }
+ }
+
+ const auto position = objectMember(value, "position");
+ if (position) {
+ optional<PropertyValue<Position>> convertedPosition =
+ convert<PropertyValue<Position>>(*position, error);
+
+ if (convertedPosition) {
+ light.setPosition(*convertedPosition);
+ } else {
+ return {};
+ }
+ }
+
+ const auto positionTransition = objectMember(value, "position-transition");
+ if (positionTransition) {
+ optional<TransitionOptions> transition =
+ convert<TransitionOptions>(*positionTransition, error);
+ if (transition) {
+ light.setPositionTransition(*transition);
+ } else {
+ return {};
+ }
+ }
+
+ const auto intensity = objectMember(value, "intensity");
+ if (intensity) {
+ optional<PropertyValue<float>> convertedIntensity =
+ convert<PropertyValue<float>>(*intensity, error);
+
+ if (convertedIntensity) {
+ light.setIntensity(*convertedIntensity);
+ } else {
+ return {};
+ }
+ }
+
+ const auto intensityTransition = objectMember(value, "intensity-transition");
+ if (intensityTransition) {
+ optional<TransitionOptions> transition =
+ convert<TransitionOptions>(*intensityTransition, error);
+ if (transition) {
+ light.setIntensityTransition(*transition);
+ } else {
+ return {};
+ }
+ }
+
+ return { std::move(light) };
+}
+
+} // namespace conversion
+} // namespace style
+} // namespace mbgl
diff --git a/src/mbgl/style/conversion/make_property_setters.hpp b/src/mbgl/style/conversion/make_property_setters.hpp
new file mode 100644
index 0000000000..074d7eb730
--- /dev/null
+++ b/src/mbgl/style/conversion/make_property_setters.hpp
@@ -0,0 +1,209 @@
+#pragma once
+
+// This file is generated. Edit make_property_setters.hpp.ejs, then run `make style-code`.
+
+#include <mbgl/style/conversion/property_setter.hpp>
+
+#include <mbgl/style/layers/fill_layer.hpp>
+#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/fill_extrusion_layer.hpp>
+#include <mbgl/style/layers/raster_layer.hpp>
+#include <mbgl/style/layers/background_layer.hpp>
+
+#include <unordered_map>
+
+namespace mbgl {
+namespace style {
+namespace conversion {
+
+inline auto makeLayoutPropertySetters() {
+ std::unordered_map<std::string, PropertySetter> result;
+
+ result["visibility"] = &setVisibility;
+
+
+ result["line-cap"] = &setProperty<LineLayer, PropertyValue<LineCapType>, &LineLayer::setLineCap>;
+ result["line-join"] = &setProperty<LineLayer, DataDrivenPropertyValue<LineJoinType>, &LineLayer::setLineJoin>;
+ result["line-miter-limit"] = &setProperty<LineLayer, PropertyValue<float>, &LineLayer::setLineMiterLimit>;
+ result["line-round-limit"] = &setProperty<LineLayer, PropertyValue<float>, &LineLayer::setLineRoundLimit>;
+
+ result["symbol-placement"] = &setProperty<SymbolLayer, PropertyValue<SymbolPlacementType>, &SymbolLayer::setSymbolPlacement>;
+ result["symbol-spacing"] = &setProperty<SymbolLayer, PropertyValue<float>, &SymbolLayer::setSymbolSpacing>;
+ result["symbol-avoid-edges"] = &setProperty<SymbolLayer, PropertyValue<bool>, &SymbolLayer::setSymbolAvoidEdges>;
+ result["icon-allow-overlap"] = &setProperty<SymbolLayer, PropertyValue<bool>, &SymbolLayer::setIconAllowOverlap>;
+ result["icon-ignore-placement"] = &setProperty<SymbolLayer, PropertyValue<bool>, &SymbolLayer::setIconIgnorePlacement>;
+ result["icon-optional"] = &setProperty<SymbolLayer, PropertyValue<bool>, &SymbolLayer::setIconOptional>;
+ result["icon-rotation-alignment"] = &setProperty<SymbolLayer, PropertyValue<AlignmentType>, &SymbolLayer::setIconRotationAlignment>;
+ result["icon-size"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<float>, &SymbolLayer::setIconSize>;
+ result["icon-text-fit"] = &setProperty<SymbolLayer, PropertyValue<IconTextFitType>, &SymbolLayer::setIconTextFit>;
+ result["icon-text-fit-padding"] = &setProperty<SymbolLayer, PropertyValue<std::array<float, 4>>, &SymbolLayer::setIconTextFitPadding>;
+ result["icon-image"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<std::string>, &SymbolLayer::setIconImage>;
+ result["icon-rotate"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<float>, &SymbolLayer::setIconRotate>;
+ result["icon-padding"] = &setProperty<SymbolLayer, PropertyValue<float>, &SymbolLayer::setIconPadding>;
+ result["icon-keep-upright"] = &setProperty<SymbolLayer, PropertyValue<bool>, &SymbolLayer::setIconKeepUpright>;
+ result["icon-offset"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<std::array<float, 2>>, &SymbolLayer::setIconOffset>;
+ result["icon-anchor"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<SymbolAnchorType>, &SymbolLayer::setIconAnchor>;
+ result["icon-pitch-alignment"] = &setProperty<SymbolLayer, PropertyValue<AlignmentType>, &SymbolLayer::setIconPitchAlignment>;
+ result["text-pitch-alignment"] = &setProperty<SymbolLayer, PropertyValue<AlignmentType>, &SymbolLayer::setTextPitchAlignment>;
+ result["text-rotation-alignment"] = &setProperty<SymbolLayer, PropertyValue<AlignmentType>, &SymbolLayer::setTextRotationAlignment>;
+ result["text-field"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<std::string>, &SymbolLayer::setTextField>;
+ result["text-font"] = &setProperty<SymbolLayer, PropertyValue<std::vector<std::string>>, &SymbolLayer::setTextFont>;
+ result["text-size"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<float>, &SymbolLayer::setTextSize>;
+ result["text-max-width"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<float>, &SymbolLayer::setTextMaxWidth>;
+ result["text-line-height"] = &setProperty<SymbolLayer, PropertyValue<float>, &SymbolLayer::setTextLineHeight>;
+ result["text-letter-spacing"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<float>, &SymbolLayer::setTextLetterSpacing>;
+ result["text-justify"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<TextJustifyType>, &SymbolLayer::setTextJustify>;
+ result["text-anchor"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<SymbolAnchorType>, &SymbolLayer::setTextAnchor>;
+ result["text-max-angle"] = &setProperty<SymbolLayer, PropertyValue<float>, &SymbolLayer::setTextMaxAngle>;
+ result["text-rotate"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<float>, &SymbolLayer::setTextRotate>;
+ result["text-padding"] = &setProperty<SymbolLayer, PropertyValue<float>, &SymbolLayer::setTextPadding>;
+ result["text-keep-upright"] = &setProperty<SymbolLayer, PropertyValue<bool>, &SymbolLayer::setTextKeepUpright>;
+ result["text-transform"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<TextTransformType>, &SymbolLayer::setTextTransform>;
+ result["text-offset"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<std::array<float, 2>>, &SymbolLayer::setTextOffset>;
+ result["text-allow-overlap"] = &setProperty<SymbolLayer, PropertyValue<bool>, &SymbolLayer::setTextAllowOverlap>;
+ result["text-ignore-placement"] = &setProperty<SymbolLayer, PropertyValue<bool>, &SymbolLayer::setTextIgnorePlacement>;
+ result["text-optional"] = &setProperty<SymbolLayer, PropertyValue<bool>, &SymbolLayer::setTextOptional>;
+
+
+
+
+
+ return result;
+}
+
+inline auto makePaintPropertySetters() {
+ std::unordered_map<std::string, PropertySetter> result;
+
+ result["fill-antialias"] = &setProperty<FillLayer, PropertyValue<bool>, &FillLayer::setFillAntialias>;
+ result["fill-antialias-transition"] = &setTransition<FillLayer, &FillLayer::setFillAntialiasTransition>;
+ result["fill-opacity"] = &setProperty<FillLayer, DataDrivenPropertyValue<float>, &FillLayer::setFillOpacity>;
+ result["fill-opacity-transition"] = &setTransition<FillLayer, &FillLayer::setFillOpacityTransition>;
+ result["fill-color"] = &setProperty<FillLayer, DataDrivenPropertyValue<Color>, &FillLayer::setFillColor>;
+ result["fill-color-transition"] = &setTransition<FillLayer, &FillLayer::setFillColorTransition>;
+ result["fill-outline-color"] = &setProperty<FillLayer, DataDrivenPropertyValue<Color>, &FillLayer::setFillOutlineColor>;
+ result["fill-outline-color-transition"] = &setTransition<FillLayer, &FillLayer::setFillOutlineColorTransition>;
+ result["fill-translate"] = &setProperty<FillLayer, PropertyValue<std::array<float, 2>>, &FillLayer::setFillTranslate>;
+ result["fill-translate-transition"] = &setTransition<FillLayer, &FillLayer::setFillTranslateTransition>;
+ result["fill-translate-anchor"] = &setProperty<FillLayer, PropertyValue<TranslateAnchorType>, &FillLayer::setFillTranslateAnchor>;
+ result["fill-translate-anchor-transition"] = &setTransition<FillLayer, &FillLayer::setFillTranslateAnchorTransition>;
+ result["fill-pattern"] = &setProperty<FillLayer, PropertyValue<std::string>, &FillLayer::setFillPattern>;
+ result["fill-pattern-transition"] = &setTransition<FillLayer, &FillLayer::setFillPatternTransition>;
+
+ result["line-opacity"] = &setProperty<LineLayer, DataDrivenPropertyValue<float>, &LineLayer::setLineOpacity>;
+ result["line-opacity-transition"] = &setTransition<LineLayer, &LineLayer::setLineOpacityTransition>;
+ result["line-color"] = &setProperty<LineLayer, DataDrivenPropertyValue<Color>, &LineLayer::setLineColor>;
+ result["line-color-transition"] = &setTransition<LineLayer, &LineLayer::setLineColorTransition>;
+ result["line-translate"] = &setProperty<LineLayer, PropertyValue<std::array<float, 2>>, &LineLayer::setLineTranslate>;
+ result["line-translate-transition"] = &setTransition<LineLayer, &LineLayer::setLineTranslateTransition>;
+ result["line-translate-anchor"] = &setProperty<LineLayer, PropertyValue<TranslateAnchorType>, &LineLayer::setLineTranslateAnchor>;
+ result["line-translate-anchor-transition"] = &setTransition<LineLayer, &LineLayer::setLineTranslateAnchorTransition>;
+ result["line-width"] = &setProperty<LineLayer, DataDrivenPropertyValue<float>, &LineLayer::setLineWidth>;
+ result["line-width-transition"] = &setTransition<LineLayer, &LineLayer::setLineWidthTransition>;
+ result["line-gap-width"] = &setProperty<LineLayer, DataDrivenPropertyValue<float>, &LineLayer::setLineGapWidth>;
+ result["line-gap-width-transition"] = &setTransition<LineLayer, &LineLayer::setLineGapWidthTransition>;
+ result["line-offset"] = &setProperty<LineLayer, DataDrivenPropertyValue<float>, &LineLayer::setLineOffset>;
+ result["line-offset-transition"] = &setTransition<LineLayer, &LineLayer::setLineOffsetTransition>;
+ result["line-blur"] = &setProperty<LineLayer, DataDrivenPropertyValue<float>, &LineLayer::setLineBlur>;
+ result["line-blur-transition"] = &setTransition<LineLayer, &LineLayer::setLineBlurTransition>;
+ result["line-dasharray"] = &setProperty<LineLayer, PropertyValue<std::vector<float>>, &LineLayer::setLineDasharray>;
+ result["line-dasharray-transition"] = &setTransition<LineLayer, &LineLayer::setLineDasharrayTransition>;
+ result["line-pattern"] = &setProperty<LineLayer, PropertyValue<std::string>, &LineLayer::setLinePattern>;
+ result["line-pattern-transition"] = &setTransition<LineLayer, &LineLayer::setLinePatternTransition>;
+
+ result["icon-opacity"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<float>, &SymbolLayer::setIconOpacity>;
+ result["icon-opacity-transition"] = &setTransition<SymbolLayer, &SymbolLayer::setIconOpacityTransition>;
+ result["icon-color"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<Color>, &SymbolLayer::setIconColor>;
+ result["icon-color-transition"] = &setTransition<SymbolLayer, &SymbolLayer::setIconColorTransition>;
+ result["icon-halo-color"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<Color>, &SymbolLayer::setIconHaloColor>;
+ result["icon-halo-color-transition"] = &setTransition<SymbolLayer, &SymbolLayer::setIconHaloColorTransition>;
+ result["icon-halo-width"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<float>, &SymbolLayer::setIconHaloWidth>;
+ result["icon-halo-width-transition"] = &setTransition<SymbolLayer, &SymbolLayer::setIconHaloWidthTransition>;
+ result["icon-halo-blur"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<float>, &SymbolLayer::setIconHaloBlur>;
+ result["icon-halo-blur-transition"] = &setTransition<SymbolLayer, &SymbolLayer::setIconHaloBlurTransition>;
+ result["icon-translate"] = &setProperty<SymbolLayer, PropertyValue<std::array<float, 2>>, &SymbolLayer::setIconTranslate>;
+ result["icon-translate-transition"] = &setTransition<SymbolLayer, &SymbolLayer::setIconTranslateTransition>;
+ result["icon-translate-anchor"] = &setProperty<SymbolLayer, PropertyValue<TranslateAnchorType>, &SymbolLayer::setIconTranslateAnchor>;
+ result["icon-translate-anchor-transition"] = &setTransition<SymbolLayer, &SymbolLayer::setIconTranslateAnchorTransition>;
+ result["text-opacity"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<float>, &SymbolLayer::setTextOpacity>;
+ result["text-opacity-transition"] = &setTransition<SymbolLayer, &SymbolLayer::setTextOpacityTransition>;
+ result["text-color"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<Color>, &SymbolLayer::setTextColor>;
+ result["text-color-transition"] = &setTransition<SymbolLayer, &SymbolLayer::setTextColorTransition>;
+ result["text-halo-color"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<Color>, &SymbolLayer::setTextHaloColor>;
+ result["text-halo-color-transition"] = &setTransition<SymbolLayer, &SymbolLayer::setTextHaloColorTransition>;
+ result["text-halo-width"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<float>, &SymbolLayer::setTextHaloWidth>;
+ result["text-halo-width-transition"] = &setTransition<SymbolLayer, &SymbolLayer::setTextHaloWidthTransition>;
+ result["text-halo-blur"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<float>, &SymbolLayer::setTextHaloBlur>;
+ result["text-halo-blur-transition"] = &setTransition<SymbolLayer, &SymbolLayer::setTextHaloBlurTransition>;
+ result["text-translate"] = &setProperty<SymbolLayer, PropertyValue<std::array<float, 2>>, &SymbolLayer::setTextTranslate>;
+ result["text-translate-transition"] = &setTransition<SymbolLayer, &SymbolLayer::setTextTranslateTransition>;
+ result["text-translate-anchor"] = &setProperty<SymbolLayer, PropertyValue<TranslateAnchorType>, &SymbolLayer::setTextTranslateAnchor>;
+ result["text-translate-anchor-transition"] = &setTransition<SymbolLayer, &SymbolLayer::setTextTranslateAnchorTransition>;
+
+ result["circle-radius"] = &setProperty<CircleLayer, DataDrivenPropertyValue<float>, &CircleLayer::setCircleRadius>;
+ result["circle-radius-transition"] = &setTransition<CircleLayer, &CircleLayer::setCircleRadiusTransition>;
+ result["circle-color"] = &setProperty<CircleLayer, DataDrivenPropertyValue<Color>, &CircleLayer::setCircleColor>;
+ result["circle-color-transition"] = &setTransition<CircleLayer, &CircleLayer::setCircleColorTransition>;
+ result["circle-blur"] = &setProperty<CircleLayer, DataDrivenPropertyValue<float>, &CircleLayer::setCircleBlur>;
+ result["circle-blur-transition"] = &setTransition<CircleLayer, &CircleLayer::setCircleBlurTransition>;
+ result["circle-opacity"] = &setProperty<CircleLayer, DataDrivenPropertyValue<float>, &CircleLayer::setCircleOpacity>;
+ result["circle-opacity-transition"] = &setTransition<CircleLayer, &CircleLayer::setCircleOpacityTransition>;
+ result["circle-translate"] = &setProperty<CircleLayer, PropertyValue<std::array<float, 2>>, &CircleLayer::setCircleTranslate>;
+ result["circle-translate-transition"] = &setTransition<CircleLayer, &CircleLayer::setCircleTranslateTransition>;
+ result["circle-translate-anchor"] = &setProperty<CircleLayer, PropertyValue<TranslateAnchorType>, &CircleLayer::setCircleTranslateAnchor>;
+ result["circle-translate-anchor-transition"] = &setTransition<CircleLayer, &CircleLayer::setCircleTranslateAnchorTransition>;
+ result["circle-pitch-scale"] = &setProperty<CircleLayer, PropertyValue<CirclePitchScaleType>, &CircleLayer::setCirclePitchScale>;
+ result["circle-pitch-scale-transition"] = &setTransition<CircleLayer, &CircleLayer::setCirclePitchScaleTransition>;
+ result["circle-pitch-alignment"] = &setProperty<CircleLayer, PropertyValue<AlignmentType>, &CircleLayer::setCirclePitchAlignment>;
+ result["circle-pitch-alignment-transition"] = &setTransition<CircleLayer, &CircleLayer::setCirclePitchAlignmentTransition>;
+ result["circle-stroke-width"] = &setProperty<CircleLayer, DataDrivenPropertyValue<float>, &CircleLayer::setCircleStrokeWidth>;
+ result["circle-stroke-width-transition"] = &setTransition<CircleLayer, &CircleLayer::setCircleStrokeWidthTransition>;
+ result["circle-stroke-color"] = &setProperty<CircleLayer, DataDrivenPropertyValue<Color>, &CircleLayer::setCircleStrokeColor>;
+ result["circle-stroke-color-transition"] = &setTransition<CircleLayer, &CircleLayer::setCircleStrokeColorTransition>;
+ result["circle-stroke-opacity"] = &setProperty<CircleLayer, DataDrivenPropertyValue<float>, &CircleLayer::setCircleStrokeOpacity>;
+ result["circle-stroke-opacity-transition"] = &setTransition<CircleLayer, &CircleLayer::setCircleStrokeOpacityTransition>;
+
+ 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>;
+ result["fill-extrusion-color-transition"] = &setTransition<FillExtrusionLayer, &FillExtrusionLayer::setFillExtrusionColorTransition>;
+ result["fill-extrusion-translate"] = &setProperty<FillExtrusionLayer, PropertyValue<std::array<float, 2>>, &FillExtrusionLayer::setFillExtrusionTranslate>;
+ result["fill-extrusion-translate-transition"] = &setTransition<FillExtrusionLayer, &FillExtrusionLayer::setFillExtrusionTranslateTransition>;
+ result["fill-extrusion-translate-anchor"] = &setProperty<FillExtrusionLayer, PropertyValue<TranslateAnchorType>, &FillExtrusionLayer::setFillExtrusionTranslateAnchor>;
+ result["fill-extrusion-translate-anchor-transition"] = &setTransition<FillExtrusionLayer, &FillExtrusionLayer::setFillExtrusionTranslateAnchorTransition>;
+ result["fill-extrusion-pattern"] = &setProperty<FillExtrusionLayer, PropertyValue<std::string>, &FillExtrusionLayer::setFillExtrusionPattern>;
+ result["fill-extrusion-pattern-transition"] = &setTransition<FillExtrusionLayer, &FillExtrusionLayer::setFillExtrusionPatternTransition>;
+ result["fill-extrusion-height"] = &setProperty<FillExtrusionLayer, DataDrivenPropertyValue<float>, &FillExtrusionLayer::setFillExtrusionHeight>;
+ result["fill-extrusion-height-transition"] = &setTransition<FillExtrusionLayer, &FillExtrusionLayer::setFillExtrusionHeightTransition>;
+ result["fill-extrusion-base"] = &setProperty<FillExtrusionLayer, DataDrivenPropertyValue<float>, &FillExtrusionLayer::setFillExtrusionBase>;
+ result["fill-extrusion-base-transition"] = &setTransition<FillExtrusionLayer, &FillExtrusionLayer::setFillExtrusionBaseTransition>;
+
+ result["raster-opacity"] = &setProperty<RasterLayer, PropertyValue<float>, &RasterLayer::setRasterOpacity>;
+ result["raster-opacity-transition"] = &setTransition<RasterLayer, &RasterLayer::setRasterOpacityTransition>;
+ result["raster-hue-rotate"] = &setProperty<RasterLayer, PropertyValue<float>, &RasterLayer::setRasterHueRotate>;
+ result["raster-hue-rotate-transition"] = &setTransition<RasterLayer, &RasterLayer::setRasterHueRotateTransition>;
+ result["raster-brightness-min"] = &setProperty<RasterLayer, PropertyValue<float>, &RasterLayer::setRasterBrightnessMin>;
+ result["raster-brightness-min-transition"] = &setTransition<RasterLayer, &RasterLayer::setRasterBrightnessMinTransition>;
+ result["raster-brightness-max"] = &setProperty<RasterLayer, PropertyValue<float>, &RasterLayer::setRasterBrightnessMax>;
+ result["raster-brightness-max-transition"] = &setTransition<RasterLayer, &RasterLayer::setRasterBrightnessMaxTransition>;
+ result["raster-saturation"] = &setProperty<RasterLayer, PropertyValue<float>, &RasterLayer::setRasterSaturation>;
+ result["raster-saturation-transition"] = &setTransition<RasterLayer, &RasterLayer::setRasterSaturationTransition>;
+ result["raster-contrast"] = &setProperty<RasterLayer, PropertyValue<float>, &RasterLayer::setRasterContrast>;
+ result["raster-contrast-transition"] = &setTransition<RasterLayer, &RasterLayer::setRasterContrastTransition>;
+ result["raster-fade-duration"] = &setProperty<RasterLayer, PropertyValue<float>, &RasterLayer::setRasterFadeDuration>;
+ result["raster-fade-duration-transition"] = &setTransition<RasterLayer, &RasterLayer::setRasterFadeDurationTransition>;
+
+ result["background-color"] = &setProperty<BackgroundLayer, PropertyValue<Color>, &BackgroundLayer::setBackgroundColor>;
+ result["background-color-transition"] = &setTransition<BackgroundLayer, &BackgroundLayer::setBackgroundColorTransition>;
+ result["background-pattern"] = &setProperty<BackgroundLayer, PropertyValue<std::string>, &BackgroundLayer::setBackgroundPattern>;
+ result["background-pattern-transition"] = &setTransition<BackgroundLayer, &BackgroundLayer::setBackgroundPatternTransition>;
+ result["background-opacity"] = &setProperty<BackgroundLayer, PropertyValue<float>, &BackgroundLayer::setBackgroundOpacity>;
+ result["background-opacity-transition"] = &setTransition<BackgroundLayer, &BackgroundLayer::setBackgroundOpacityTransition>;
+
+ return result;
+}
+
+} // namespace conversion
+} // namespace style
+} // namespace mbgl
diff --git a/src/mbgl/style/conversion/make_property_setters.hpp.ejs b/src/mbgl/style/conversion/make_property_setters.hpp.ejs
new file mode 100644
index 0000000000..2975cb19f2
--- /dev/null
+++ b/src/mbgl/style/conversion/make_property_setters.hpp.ejs
@@ -0,0 +1,46 @@
+#pragma once
+
+// This file is generated. Edit make_property_setters.hpp.ejs, then run `make style-code`.
+
+#include <mbgl/style/conversion/property_setter.hpp>
+
+<% for (const layer of locals.layers) { -%>
+#include <mbgl/style/layers/<%- layer.type.replace('-', '_') %>_layer.hpp>
+<% } -%>
+
+#include <unordered_map>
+
+namespace mbgl {
+namespace style {
+namespace conversion {
+
+inline auto makeLayoutPropertySetters() {
+ std::unordered_map<std::string, PropertySetter> result;
+
+ result["visibility"] = &setVisibility;
+
+<% for (const layer of locals.layers) { -%>
+<% for (const property of layer.layoutProperties) { -%>
+ result["<%- property.name %>"] = &setProperty<<%- camelize(layer.type) %>Layer, <%- propertyValueType(property) %>, &<%- camelize(layer.type) %>Layer::set<%- camelize(property.name) %>>;
+<% } -%>
+
+<% } -%>
+ return result;
+}
+
+inline auto makePaintPropertySetters() {
+ std::unordered_map<std::string, PropertySetter> result;
+
+<% for (const layer of locals.layers) { -%>
+<% for (const property of layer.paintProperties) { -%>
+ result["<%- property.name %>"] = &setProperty<<%- camelize(layer.type) %>Layer, <%- propertyValueType(property) %>, &<%- camelize(layer.type) %>Layer::set<%- camelize(property.name) %>>;
+ result["<%- property.name %>-transition"] = &setTransition<<%- camelize(layer.type) %>Layer, &<%- camelize(layer.type) %>Layer::set<%- camelize(property.name) %>Transition>;
+<% } -%>
+
+<% } -%>
+ return result;
+}
+
+} // namespace conversion
+} // namespace style
+} // namespace mbgl
diff --git a/src/mbgl/style/conversion/position.cpp b/src/mbgl/style/conversion/position.cpp
new file mode 100644
index 0000000000..702d250dbf
--- /dev/null
+++ b/src/mbgl/style/conversion/position.cpp
@@ -0,0 +1,22 @@
+#include <mbgl/style/conversion/position.hpp>
+#include <mbgl/style/conversion/constant.hpp>
+
+#include <array>
+
+namespace mbgl {
+namespace style {
+namespace conversion {
+
+optional<Position> Converter<Position>::operator()(const Convertible& value, Error& error) const {
+ optional<std::array<float, 3>> spherical = convert<std::array<float, 3>>(value, error);
+
+ if (!spherical) {
+ return {};
+ }
+
+ return Position(*spherical);
+}
+
+} // namespace conversion
+} // namespace style
+} // namespace mbgl
diff --git a/include/mbgl/style/conversion/property_setter.hpp b/src/mbgl/style/conversion/property_setter.hpp
index 759c4512cc..9e382b9c38 100644
--- a/include/mbgl/style/conversion/property_setter.hpp
+++ b/src/mbgl/style/conversion/property_setter.hpp
@@ -13,11 +13,10 @@ namespace mbgl {
namespace style {
namespace conversion {
-template <class V>
-using PropertySetter = optional<Error> (*) (Layer&, const V&);
+using PropertySetter = optional<Error> (*) (Layer&, const Convertible&);
-template <class V, class L, class PropertyValue, void (L::*setter)(PropertyValue)>
-optional<Error> setProperty(Layer& layer, const V& value) {
+template <class L, class PropertyValue, void (L::*setter)(PropertyValue)>
+optional<Error> setProperty(Layer& layer, const Convertible& value) {
auto* typedLayer = layer.as<L>();
if (!typedLayer) {
return Error { "layer doesn't support this property" };
@@ -33,8 +32,8 @@ optional<Error> setProperty(Layer& layer, const V& value) {
return {};
}
-template <class V, class L, void (L::*setter)(const TransitionOptions&)>
-optional<Error> setTransition(Layer& layer, const V& value) {
+template <class L, void (L::*setter)(const TransitionOptions&)>
+optional<Error> setTransition(Layer& layer, const Convertible& value) {
auto* typedLayer = layer.as<L>();
if (!typedLayer) {
return Error { "layer doesn't support this property" };
@@ -50,8 +49,7 @@ optional<Error> setTransition(Layer& layer, const V& value) {
return {};
}
-template <class V>
-optional<Error> setVisibility(Layer& layer, const V& value) {
+inline optional<Error> setVisibility(Layer& layer, const Convertible& value) {
if (isUndefined(value)) {
layer.setVisibility(VisibilityType::Visible);
return {};
diff --git a/src/mbgl/style/conversion/source.cpp b/src/mbgl/style/conversion/source.cpp
new file mode 100644
index 0000000000..c10d0babcf
--- /dev/null
+++ b/src/mbgl/style/conversion/source.cpp
@@ -0,0 +1,175 @@
+#include <mbgl/style/conversion/source.hpp>
+#include <mbgl/style/conversion/coordinate.hpp>
+#include <mbgl/style/conversion/geojson.hpp>
+#include <mbgl/style/conversion/geojson_options.hpp>
+#include <mbgl/style/conversion/tileset.hpp>
+#include <mbgl/style/sources/geojson_source.hpp>
+#include <mbgl/style/sources/raster_source.hpp>
+#include <mbgl/style/sources/vector_source.hpp>
+#include <mbgl/style/sources/image_source.hpp>
+#include <mbgl/util/geo.hpp>
+
+namespace mbgl {
+namespace style {
+namespace conversion {
+
+// A tile source can either specify a URL to TileJSON, or inline TileJSON.
+static optional<variant<std::string, Tileset>> convertURLOrTileset(const Convertible& value, Error& error) {
+ auto urlVal = objectMember(value, "url");
+ if (!urlVal) {
+ optional<Tileset> tileset = convert<Tileset>(value, error);
+ if (!tileset) {
+ return {};
+ }
+ return { *tileset };
+ }
+
+ optional<std::string> url = toString(*urlVal);
+ if (!url) {
+ error = { "source url must be a string" };
+ return {};
+ }
+
+ return { *url };
+}
+
+static optional<std::unique_ptr<Source>> convertRasterSource(const std::string& id,
+ const Convertible& value,
+ Error& error) {
+ optional<variant<std::string, Tileset>> urlOrTileset = convertURLOrTileset(value, error);
+ if (!urlOrTileset) {
+ return {};
+ }
+
+ uint16_t tileSize = util::tileSize;
+ auto tileSizeValue = objectMember(value, "tileSize");
+ if (tileSizeValue) {
+ optional<float> size = toNumber(*tileSizeValue);
+ if (!size || *size < 0 || *size > std::numeric_limits<uint16_t>::max()) {
+ error = { "invalid tileSize" };
+ return {};
+ }
+ tileSize = *size;
+ }
+
+ return { std::make_unique<RasterSource>(id, std::move(*urlOrTileset), tileSize) };
+}
+
+static optional<std::unique_ptr<Source>> convertVectorSource(const std::string& id,
+ const Convertible& value,
+ Error& error) {
+ optional<variant<std::string, Tileset>> urlOrTileset = convertURLOrTileset(value, error);
+ if (!urlOrTileset) {
+ return {};
+ }
+
+ return { std::make_unique<VectorSource>(id, std::move(*urlOrTileset)) };
+}
+
+static optional<std::unique_ptr<Source>> convertGeoJSONSource(const std::string& id,
+ const Convertible& value,
+ Error& error) {
+ auto dataValue = objectMember(value, "data");
+ if (!dataValue) {
+ error = { "GeoJSON source must have a data value" };
+ return {};
+ }
+
+ optional<GeoJSONOptions> options = convert<GeoJSONOptions>(value, error);
+ if (!options) {
+ return {};
+ }
+
+ auto result = std::make_unique<GeoJSONSource>(id, *options);
+
+ if (isObject(*dataValue)) {
+ optional<GeoJSON> geoJSON = convert<GeoJSON>(*dataValue, error);
+ if (!geoJSON) {
+ return {};
+ }
+ result->setGeoJSON(std::move(*geoJSON));
+ } else if (toString(*dataValue)) {
+ result->setURL(*toString(*dataValue));
+ } else {
+ error = { "GeoJSON data must be a URL or an object" };
+ return {};
+ }
+
+ return { std::move(result) };
+}
+
+static optional<std::unique_ptr<Source>> convertImageSource(const std::string& id,
+ const Convertible& value,
+ Error& error) {
+ auto urlValue = objectMember(value, "url");
+ if (!urlValue) {
+ error = { "Image source must have a url value" };
+ return {};
+ }
+
+ auto urlString = toString(*urlValue);
+ if (!urlString) {
+ error = { "Image url must be a URL string" };
+ return {};
+ }
+
+ auto coordinatesValue = objectMember(value, "coordinates");
+ if (!coordinatesValue) {
+ error = { "Image source must have a coordinates values" };
+ return {};
+ }
+
+ if (!isArray(*coordinatesValue) || arrayLength(*coordinatesValue) != 4) {
+ error = { "Image coordinates must be an array of four longitude latitude pairs" };
+ return {};
+ }
+
+ std::array<LatLng, 4> coordinates;
+ for (std::size_t i=0; i < 4; i++) {
+ auto latLng = conversion::convert<LatLng>(arrayMember(*coordinatesValue,i), error);
+ if (!latLng) {
+ return {};
+ }
+ coordinates[i] = *latLng;
+ }
+ auto result = std::make_unique<ImageSource>(id, coordinates);
+ result->setURL(*urlString);
+
+ return { std::move(result) };
+}
+
+optional<std::unique_ptr<Source>> Converter<std::unique_ptr<Source>>::operator()(const Convertible& value, Error& error, const std::string& id) const {
+ if (!isObject(value)) {
+ error = { "source must be an object" };
+ return {};
+ }
+
+ auto typeValue = objectMember(value, "type");
+ if (!typeValue) {
+ error = { "source must have a type" };
+ return {};
+ }
+
+ optional<std::string> type = toString(*typeValue);
+ if (!type) {
+ error = { "source type must be a string" };
+ return {};
+ }
+
+ if (*type == "raster") {
+ return convertRasterSource(id, value, error);
+ } else if (*type == "vector") {
+ return convertVectorSource(id, value, error);
+ } else if (*type == "geojson") {
+ return convertGeoJSONSource(id, value, error);
+ } else if (*type == "image") {
+ return convertImageSource(id, value, error);
+ } else {
+ error = { "invalid source type" };
+ return {};
+ }
+}
+
+} // namespace conversion
+} // namespace style
+} // namespace mbgl
diff --git a/src/mbgl/style/conversion/tileset.cpp b/src/mbgl/style/conversion/tileset.cpp
new file mode 100644
index 0000000000..b9383c41b8
--- /dev/null
+++ b/src/mbgl/style/conversion/tileset.cpp
@@ -0,0 +1,73 @@
+#include <mbgl/style/conversion/tileset.hpp>
+
+namespace mbgl {
+namespace style {
+namespace conversion {
+
+optional<Tileset> Converter<Tileset>::operator()(const Convertible& value, Error& error) const {
+ Tileset result;
+
+ auto tiles = objectMember(value, "tiles");
+ if (!tiles) {
+ error = { "source must have tiles" };
+ return {};
+ }
+
+ if (!isArray(*tiles)) {
+ error = { "source tiles must be an array" };
+ return {};
+ }
+
+ for (std::size_t i = 0; i < arrayLength(*tiles); i++) {
+ optional<std::string> urlTemplate = toString(arrayMember(*tiles, i));
+ if (!urlTemplate) {
+ error = { "source tiles member must be a string" };
+ return {};
+ }
+ result.tiles.push_back(std::move(*urlTemplate));
+ }
+
+ auto schemeValue = objectMember(value, "scheme");
+ if (schemeValue) {
+ optional<std::string> scheme = toString(*schemeValue);
+ if (scheme && *scheme == "tms") {
+ result.scheme = Tileset::Scheme::TMS;
+ }
+ }
+
+ auto minzoomValue = objectMember(value, "minzoom");
+ if (minzoomValue) {
+ optional<float> minzoom = toNumber(*minzoomValue);
+ if (!minzoom || *minzoom < 0 || *minzoom > std::numeric_limits<uint8_t>::max()) {
+ error = { "invalid minzoom" };
+ return {};
+ }
+ result.zoomRange.min = *minzoom;
+ }
+
+ auto maxzoomValue = objectMember(value, "maxzoom");
+ if (maxzoomValue) {
+ optional<float> maxzoom = toNumber(*maxzoomValue);
+ if (!maxzoom || *maxzoom < 0 || *maxzoom > std::numeric_limits<uint8_t>::max()) {
+ error = { "invalid maxzoom" };
+ return {};
+ }
+ result.zoomRange.max = *maxzoom;
+ }
+
+ auto attributionValue = objectMember(value, "attribution");
+ if (attributionValue) {
+ optional<std::string> attribution = toString(*attributionValue);
+ if (!attribution) {
+ error = { "source attribution must be a string" };
+ return {};
+ }
+ result.attribution = std::move(*attribution);
+ }
+
+ return result;
+}
+
+} // namespace conversion
+} // namespace style
+} // namespace mbgl
diff --git a/src/mbgl/style/conversion/transition_options.cpp b/src/mbgl/style/conversion/transition_options.cpp
new file mode 100644
index 0000000000..8a60c5bfd8
--- /dev/null
+++ b/src/mbgl/style/conversion/transition_options.cpp
@@ -0,0 +1,40 @@
+#include <mbgl/style/conversion/transition_options.hpp>
+
+namespace mbgl {
+namespace style {
+namespace conversion {
+
+optional<TransitionOptions> Converter<TransitionOptions>::operator()(const Convertible& value, Error& error) const {
+ if (!isObject(value)) {
+ error = { "transition must be an object" };
+ return {};
+ }
+
+ TransitionOptions result;
+
+ auto duration = objectMember(value, "duration");
+ if (duration) {
+ auto number = toNumber(*duration);
+ if (!number) {
+ error = { "duration must be a number" };
+ return {};
+ }
+ result.duration = { std::chrono::milliseconds(int64_t(*number)) };
+ }
+
+ auto delay = objectMember(value, "delay");
+ if (delay) {
+ auto number = toNumber(*delay);
+ if (!number) {
+ error = { "delay must be a number" };
+ return {};
+ }
+ result.delay = { std::chrono::milliseconds(int64_t(*number)) };
+ }
+
+ return result;
+}
+
+} // namespace conversion
+} // namespace style
+} // namespace mbgl
diff --git a/src/mbgl/style/expression/array_assertion.cpp b/src/mbgl/style/expression/array_assertion.cpp
new file mode 100644
index 0000000000..a62f67fbb5
--- /dev/null
+++ b/src/mbgl/style/expression/array_assertion.cpp
@@ -0,0 +1,85 @@
+#include <mbgl/style/expression/array_assertion.hpp>
+#include <mbgl/style/expression/check_subtype.hpp>
+
+namespace mbgl {
+namespace style {
+namespace expression {
+
+EvaluationResult ArrayAssertion::evaluate(const EvaluationContext& params) const {
+ auto result = input->evaluate(params);
+ if (!result) {
+ return result.error();
+ }
+ type::Type expected = getType();
+ type::Type actual = typeOf(*result);
+ if (checkSubtype(expected, actual)) {
+ return EvaluationError {
+ "Expected value to be of type " + toString(expected) +
+ ", but found " + toString(actual) + " instead."
+ };
+ }
+ return *result;
+}
+
+void ArrayAssertion::eachChild(const std::function<void(const Expression&)>& visit) const {
+ visit(*input);
+}
+
+using namespace mbgl::style::conversion;
+ParseResult ArrayAssertion::parse(const Convertible& value, ParsingContext& ctx) {
+
+ static std::unordered_map<std::string, type::Type> itemTypes {
+ {"string", type::String},
+ {"number", type::Number},
+ {"boolean", type::Boolean}
+ };
+
+ auto length = arrayLength(value);
+ if (length < 2 || length > 4) {
+ ctx.error("Expected 1, 2, or 3 arguments, but found " + std::to_string(length - 1) + " instead.");
+ return ParseResult();
+ }
+
+ optional<type::Type> itemType;
+ optional<std::size_t> N;
+ if (length > 2) {
+ optional<std::string> itemTypeName = toString(arrayMember(value, 1));
+ auto it = itemTypeName ? itemTypes.find(*itemTypeName) : itemTypes.end();
+ if (it == itemTypes.end()) {
+ ctx.error(
+ R"(The item type argument of "array" must be one of string, number, boolean)",
+ 1
+ );
+ return ParseResult();
+ }
+ itemType = it->second;
+ } else {
+ itemType = {type::Value};
+ }
+
+ if (length > 3) {
+ auto n = toNumber(arrayMember(value, 2));
+ if (!n || *n != std::floor(*n)) {
+ ctx.error(
+ R"(The length argument to "array" must be a positive integer literal.)",
+ 2
+ );
+ return ParseResult();
+ }
+ N = optional<std::size_t>(*n);
+ }
+
+ auto input = ctx.parse(arrayMember(value, length - 1), length - 1, {type::Value});
+ if (!input) {
+ return input;
+ }
+
+ return ParseResult(std::make_unique<ArrayAssertion>(
+ type::Array(*itemType, N),
+ std::move(*input)
+ ));
+}
+
+} // namespace expression
+} // namespace style
+} // namespace mbgl
diff --git a/src/mbgl/style/expression/assertion.cpp b/src/mbgl/style/expression/assertion.cpp
new file mode 100644
index 0000000000..a17c53cf54
--- /dev/null
+++ b/src/mbgl/style/expression/assertion.cpp
@@ -0,0 +1,73 @@
+#include <mbgl/style/expression/assertion.hpp>
+#include <mbgl/style/expression/check_subtype.hpp>
+
+namespace mbgl {
+namespace style {
+namespace expression {
+
+using namespace mbgl::style::conversion;
+ParseResult Assertion::parse(const Convertible& value, ParsingContext& ctx) {
+ static std::unordered_map<std::string, type::Type> types {
+ {"string", type::String},
+ {"number", type::Number},
+ {"boolean", type::Boolean},
+ {"object", type::Object}
+ };
+
+ std::size_t length = arrayLength(value);
+
+ if (length < 2) {
+ ctx.error("Expected at least one argument.");
+ return ParseResult();
+ }
+
+ auto it = types.find(*toString(arrayMember(value, 0)));
+ assert(it != types.end());
+
+ std::vector<std::unique_ptr<Expression>> parsed;
+ parsed.reserve(length - 1);
+ for (std::size_t i = 1; i < length; i++) {
+ ParseResult input = ctx.parse(arrayMember(value, i), i, {type::Value});
+ if (!input) return ParseResult();
+ parsed.push_back(std::move(*input));
+ }
+
+ return ParseResult(std::make_unique<Assertion>(it->second, std::move(parsed)));
+}
+
+EvaluationResult Assertion::evaluate(const EvaluationContext& params) const {
+ for (std::size_t i = 0; i < inputs.size(); i++) {
+ EvaluationResult value = inputs[i]->evaluate(params);
+ if (!value) return value;
+ if (!type::checkSubtype(getType(), typeOf(*value))) {
+ return value;
+ } else if (i == inputs.size() - 1) {
+ return EvaluationError {
+ "Expected value to be of type " + toString(getType()) +
+ ", but found " + toString(typeOf(*value)) + " instead."
+ };
+ }
+ }
+
+ assert(false);
+ return EvaluationError { "Unreachable" };
+};
+
+void Assertion::eachChild(const std::function<void(const Expression&)>& visit) const {
+ for(const std::unique_ptr<Expression>& input : inputs) {
+ visit(*input);
+ }
+};
+
+bool Assertion::operator==(const Expression& e) const {
+ if (auto rhs = dynamic_cast<const Assertion*>(&e)) {
+ return getType() == rhs->getType() && Expression::childrenEqual(inputs, rhs->inputs);
+ }
+ return false;
+}
+
+} // namespace expression
+} // namespace style
+} // namespace mbgl
+
+
diff --git a/src/mbgl/style/expression/at.cpp b/src/mbgl/style/expression/at.cpp
new file mode 100644
index 0000000000..d9beb63b52
--- /dev/null
+++ b/src/mbgl/style/expression/at.cpp
@@ -0,0 +1,63 @@
+#include <mbgl/style/expression/at.hpp>
+
+
+namespace mbgl {
+namespace style {
+namespace expression {
+
+EvaluationResult At::evaluate(const EvaluationContext& params) const {
+ const EvaluationResult evaluatedIndex = index->evaluate(params);
+ const EvaluationResult evaluatedInput = input->evaluate(params);
+ if (!evaluatedIndex) {
+ return evaluatedIndex.error();
+ }
+ if (!evaluatedInput) {
+ return evaluatedInput.error();
+ }
+
+ const auto i = evaluatedIndex->get<double>();
+ const auto inputArray = evaluatedInput->get<std::vector<Value>>();
+
+ if (i < 0 || i >= inputArray.size()) {
+ return EvaluationError {
+ "Array index out of bounds: " + stringify(i) +
+ " > " + std::to_string(inputArray.size()) + "."
+ };
+ }
+ if (i != std::floor(i)) {
+ return EvaluationError {
+ "Array index must be an integer, but found " + stringify(i) + " instead."
+ };
+ }
+ return inputArray[static_cast<std::size_t>(i)];
+}
+
+void At::eachChild(const std::function<void(const Expression&)>& visit) const {
+ visit(*index);
+ visit(*input);
+}
+
+using namespace mbgl::style::conversion;
+ParseResult At::parse(const Convertible& value, ParsingContext& ctx) {
+ assert(isArray(value));
+
+ std::size_t length = arrayLength(value);
+ if (length != 3) {
+ ctx.error("Expected 2 arguments, but found " + std::to_string(length - 1) + " instead.");
+ return ParseResult();
+ }
+
+ ParseResult index = ctx.parse(arrayMember(value, 1), 1, {type::Number});
+
+ type::Type inputType = type::Array(ctx.getExpected() ? *ctx.getExpected() : type::Value);
+ ParseResult input = ctx.parse(arrayMember(value, 2), 2, {inputType});
+
+ if (!index || !input) return ParseResult();
+
+ return ParseResult(std::make_unique<At>(std::move(*index), std::move(*input)));
+
+}
+
+} // namespace expression
+} // namespace style
+} // namespace mbgl
diff --git a/src/mbgl/style/expression/boolean_operator.cpp b/src/mbgl/style/expression/boolean_operator.cpp
new file mode 100644
index 0000000000..88797f965a
--- /dev/null
+++ b/src/mbgl/style/expression/boolean_operator.cpp
@@ -0,0 +1,87 @@
+#include <mbgl/style/expression/boolean_operator.hpp>
+
+namespace mbgl {
+namespace style {
+namespace expression {
+
+EvaluationResult Any::evaluate(const EvaluationContext& params) const {
+ for (auto it = inputs.begin(); it != inputs.end(); it++) {
+ const EvaluationResult result = (*it)->evaluate(params);
+ if (!result) return result;
+ if (result->get<bool>()) return EvaluationResult(true);
+ }
+ return EvaluationResult(false);
+}
+
+void Any::eachChild(const std::function<void(const Expression&)>& visit) const {
+ for (const std::unique_ptr<Expression>& input : inputs) {
+ visit(*input);
+ }
+}
+
+bool Any::operator==(const Expression& e) const {
+ if (auto rhs = dynamic_cast<const Any*>(&e)) {
+ return Expression::childrenEqual(inputs, rhs->inputs);
+ }
+ return false;
+}
+
+
+EvaluationResult All::evaluate(const EvaluationContext& params) const {
+ for (auto it = inputs.begin(); it != inputs.end(); it++) {
+ const EvaluationResult result = (*it)->evaluate(params);
+ if (!result) return result;
+ if (!result->get<bool>()) return EvaluationResult(false);
+ }
+ return EvaluationResult(true);
+}
+
+void All::eachChild(const std::function<void(const Expression&)>& visit) const {
+ for (const std::unique_ptr<Expression>& input : inputs) {
+ visit(*input);
+ }
+}
+
+bool All::operator==(const Expression& e) const {
+ if (auto rhs = dynamic_cast<const All*>(&e)) {
+ return Expression::childrenEqual(inputs, rhs->inputs);
+ }
+ return false;
+}
+
+using namespace mbgl::style::conversion;
+
+template <class T>
+ParseResult parseBooleanOp(const Convertible& value, ParsingContext& ctx) {
+
+ assert(isArray(value));
+ auto length = arrayLength(value);
+
+ std::vector<std::unique_ptr<Expression>> parsedInputs;
+
+ parsedInputs.reserve(length - 1);
+ for (std::size_t i = 1; i < length; i++) {
+ auto parsed = ctx.parse(arrayMember(value, i), i, {type::Boolean});
+ if (!parsed) {
+ return parsed;
+ }
+
+ parsedInputs.push_back(std::move(*parsed));
+ }
+
+ return ParseResult(std::make_unique<T>(std::move(parsedInputs)));
+}
+
+ParseResult Any::parse(const Convertible& value, ParsingContext& ctx) {
+ return parseBooleanOp<Any>(value, ctx);
+}
+
+ParseResult All::parse(const Convertible& value, ParsingContext& ctx) {
+ return parseBooleanOp<All>(value, ctx);
+}
+
+
+} // namespace expression
+} // namespace style
+} // namespace mbgl
+
diff --git a/src/mbgl/style/expression/case.cpp b/src/mbgl/style/expression/case.cpp
new file mode 100644
index 0000000000..a435b71fc5
--- /dev/null
+++ b/src/mbgl/style/expression/case.cpp
@@ -0,0 +1,90 @@
+#include <mbgl/style/expression/case.hpp>
+
+namespace mbgl {
+namespace style {
+namespace expression {
+
+EvaluationResult Case::evaluate(const EvaluationContext& params) const {
+ for (const auto& branch : branches) {
+ const EvaluationResult evaluatedTest = branch.first->evaluate(params);
+ if (!evaluatedTest) {
+ return evaluatedTest.error();
+ }
+ if (evaluatedTest->get<bool>()) {
+ return branch.second->evaluate(params);
+ }
+ }
+
+ return otherwise->evaluate(params);
+}
+
+void Case::eachChild(const std::function<void(const Expression&)>& visit) const {
+ for (const Branch& branch : branches) {
+ visit(*branch.first);
+ visit(*branch.second);
+ }
+ visit(*otherwise);
+}
+
+bool Case::operator==(const Expression& e) const {
+ if (auto rhs = dynamic_cast<const Case*>(&e)) {
+ return *otherwise == *(rhs->otherwise) && Expression::childrenEqual(branches, rhs->branches);
+ }
+ return false;
+}
+
+using namespace mbgl::style::conversion;
+ParseResult Case::parse(const Convertible& value, ParsingContext& ctx) {
+ assert(isArray(value));
+ auto length = arrayLength(value);
+ if (length < 4) {
+ ctx.error("Expected at least 3 arguments, but found only " + std::to_string(length - 1) + ".");
+ return ParseResult();
+ }
+
+ // Expect even-length array: ["case", 2 * (n pairs)..., otherwise]
+ if (length % 2 != 0) {
+ ctx.error("Expected an odd number of arguments");
+ return ParseResult();
+ }
+
+ optional<type::Type> outputType;
+ if (ctx.getExpected() && *ctx.getExpected() != type::Value) {
+ outputType = ctx.getExpected();
+ }
+
+ std::vector<Case::Branch> branches;
+ branches.reserve((length - 2) / 2);
+ for (size_t i = 1; i + 1 < length; i += 2) {
+ auto test = ctx.parse(arrayMember(value, i), i, {type::Boolean});
+ if (!test) {
+ return test;
+ }
+
+ auto output = ctx.parse(arrayMember(value, i + 1), i + 1, outputType);
+ if (!output) {
+ return output;
+ }
+
+ if (!outputType) {
+ outputType = (*output)->getType();
+ }
+
+ branches.push_back(std::make_pair(std::move(*test), std::move(*output)));
+ }
+
+ assert(outputType);
+
+ auto otherwise = ctx.parse(arrayMember(value, length - 1), length - 1, outputType);
+ if (!otherwise) {
+ return otherwise;
+ }
+
+ return ParseResult(std::make_unique<Case>(*outputType,
+ std::move(branches),
+ std::move(*otherwise)));
+}
+
+} // namespace expression
+} // namespace style
+} // namespace mbgl
diff --git a/src/mbgl/style/expression/check_subtype.cpp b/src/mbgl/style/expression/check_subtype.cpp
new file mode 100644
index 0000000000..04a1643f0c
--- /dev/null
+++ b/src/mbgl/style/expression/check_subtype.cpp
@@ -0,0 +1,60 @@
+#include <string>
+#include <mbgl/style/expression/check_subtype.hpp>
+
+namespace mbgl {
+namespace style {
+namespace expression {
+namespace type {
+
+std::string errorMessage(const Type& expected, const Type& t) {
+ return {"Expected " + toString(expected) + " but found " + toString(t) + " instead."};
+}
+
+optional<std::string> checkSubtype(const Type& expected, const Type& t) {
+ if (t.is<ErrorType>()) return {};
+
+ optional<std::string> result = expected.match(
+ [&] (const Array& expectedArray) -> optional<std::string> {
+ if (!t.is<Array>()) { return {errorMessage(expected, t)}; }
+ const auto& actualArray = t.get<Array>();
+ const auto err = checkSubtype(expectedArray.itemType, actualArray.itemType);
+ if (err) return { errorMessage(expected, t) };
+ if (expectedArray.N && expectedArray.N != actualArray.N) return { errorMessage(expected, t) };
+ return {};
+ },
+ [&] (const ValueType&) -> optional<std::string> {
+ if (t.is<ValueType>()) return {};
+
+ const Type members[] = {
+ Null,
+ Boolean,
+ Number,
+ String,
+ Object,
+ Color,
+ Array(Value)
+ };
+
+ for (const auto& member : members) {
+ const auto err = checkSubtype(member, t);
+ if (!err) {
+ return {};
+ }
+ }
+ return { errorMessage(expected, t) };
+ },
+ [&] (const auto&) -> optional<std::string> {
+ if (expected != t) {
+ return { errorMessage(expected, t) };
+ }
+ return {};
+ }
+ );
+
+ return result;
+}
+
+} // namespace type
+} // namespace expression
+} // namespace style
+} // namespace mbgl
diff --git a/src/mbgl/style/expression/coalesce.cpp b/src/mbgl/style/expression/coalesce.cpp
new file mode 100644
index 0000000000..bfde3c7581
--- /dev/null
+++ b/src/mbgl/style/expression/coalesce.cpp
@@ -0,0 +1,62 @@
+#include <mbgl/style/expression/coalesce.hpp>
+
+namespace mbgl {
+namespace style {
+namespace expression {
+
+EvaluationResult Coalesce::evaluate(const EvaluationContext& params) const {
+ EvaluationResult result = Null;
+ for (const auto& arg : args) {
+ result = arg->evaluate(params);
+ if (!result || *result != Null) break;
+ }
+ return result;
+}
+
+void Coalesce::eachChild(const std::function<void(const Expression&)>& visit) const {
+ for (const std::unique_ptr<Expression>& arg : args) {
+ visit(*arg);
+ }
+}
+
+bool Coalesce::operator==(const Expression& e) const {
+ if (auto rhs = dynamic_cast<const Coalesce*>(&e)) {
+ return Expression::childrenEqual(args, rhs->args);
+ }
+ return false;
+}
+
+using namespace mbgl::style::conversion;
+ParseResult Coalesce::parse(const Convertible& value, ParsingContext& ctx) {
+ assert(isArray(value));
+ auto length = arrayLength(value);
+ if (length < 2) {
+ ctx.error("Expected at least one argument.");
+ return ParseResult();
+ }
+
+ optional<type::Type> outputType;
+ if (ctx.getExpected() && *ctx.getExpected() != type::Value) {
+ outputType = ctx.getExpected();
+ }
+
+ Coalesce::Args args;
+ args.reserve(length - 1);
+ for (std::size_t i = 1; i < length; i++) {
+ auto parsed = ctx.parse(arrayMember(value, i), i, outputType);
+ if (!parsed) {
+ return parsed;
+ }
+ if (!outputType) {
+ outputType = (*parsed)->getType();
+ }
+ args.push_back(std::move(*parsed));
+ }
+
+ assert(outputType);
+ return ParseResult(std::make_unique<Coalesce>(*outputType, std::move(args)));
+}
+
+} // namespace expression
+} // namespace style
+} // namespace mbgl
diff --git a/src/mbgl/style/expression/coercion.cpp b/src/mbgl/style/expression/coercion.cpp
new file mode 100644
index 0000000000..f2042ffd8f
--- /dev/null
+++ b/src/mbgl/style/expression/coercion.cpp
@@ -0,0 +1,143 @@
+#include <mbgl/style/expression/coercion.hpp>
+#include <mbgl/style/expression/check_subtype.hpp>
+#include <mbgl/style/expression/util.hpp>
+
+namespace mbgl {
+namespace style {
+namespace expression {
+
+EvaluationResult toNumber(const Value& v) {
+ optional<double> result = v.match(
+ [](const double f) -> optional<double> { return f; },
+ [](const std::string& s) -> optional<double> {
+ try {
+ return std::stof(s);
+ } catch(std::exception) {
+ return optional<double>();
+ }
+ },
+ [](const auto&) { return optional<double>(); }
+ );
+ if (!result) {
+ return EvaluationError {
+ "Could not convert " + stringify(v) + " to number."
+ };
+ }
+ return *result;
+}
+
+EvaluationResult toColor(const Value& colorValue) {
+ return colorValue.match(
+ [&](const std::string& colorString) -> EvaluationResult {
+ const optional<Color> result = Color::parse(colorString);
+ if (result) {
+ return *result;
+ } else {
+ return EvaluationError{
+ "Could not parse color from value '" + colorString + "'"
+ };
+ }
+ },
+ [&](const std::vector<Value>& components) -> EvaluationResult {
+ std::size_t len = components.size();
+ bool isNumeric = std::all_of(components.begin(), components.end(), [](const Value& item) {
+ return item.template is<double>();
+ });
+ if ((len == 3 || len == 4) && isNumeric) {
+ Result<Color> c = {rgba(
+ components[0].template get<double>(),
+ components[1].template get<double>(),
+ components[2].template get<double>(),
+ len == 4 ? components[3].template get<double>() : 1.0
+ )};
+ if (!c) return c.error();
+ return *c;
+ } else {
+ return EvaluationError{
+ "Invalid rbga value " + stringify(colorValue) + ": expected an array containing either three or four numeric values."
+ };
+ }
+ },
+ [&](const auto&) -> EvaluationResult {
+ return EvaluationError{
+ "Could not parse color from value '" + stringify(colorValue) + "'"
+ };
+ }
+ );
+}
+
+Coercion::Coercion(type::Type type_, std::vector<std::unique_ptr<Expression>> inputs_) :
+ Expression(std::move(type_)),
+ inputs(std::move(inputs_))
+{
+ type::Type t = getType();
+ if (t.is<type::NumberType>()) {
+ coerceSingleValue = toNumber;
+ } else if (t.is<type::ColorType>()) {
+ coerceSingleValue = toColor;
+ } else {
+ assert(false);
+ }
+}
+
+using namespace mbgl::style::conversion;
+ParseResult Coercion::parse(const Convertible& value, ParsingContext& ctx) {
+ static std::unordered_map<std::string, type::Type> types {
+ {"to-number", type::Number},
+ {"to-color", type::Color}
+ };
+
+ std::size_t length = arrayLength(value);
+
+ if (length < 2) {
+ ctx.error("Expected at least one argument.");
+ return ParseResult();
+ }
+
+ auto it = types.find(*toString(arrayMember(value, 0)));
+ assert(it != types.end());
+
+ std::vector<std::unique_ptr<Expression>> parsed;
+ parsed.reserve(length - 1);
+ for (std::size_t i = 1; i < length; i++) {
+ ParseResult input = ctx.parse(arrayMember(value, i), i, {type::Value});
+ if (!input) return ParseResult();
+ parsed.push_back(std::move(*input));
+ }
+
+ return ParseResult(std::make_unique<Coercion>(it->second, std::move(parsed)));
+}
+
+EvaluationResult Coercion::evaluate(const EvaluationContext& params) const {
+ for (std::size_t i = 0; i < inputs.size(); i++) {
+ EvaluationResult value = inputs[i]->evaluate(params);
+ if (!value) return value;
+ EvaluationResult coerced = coerceSingleValue(*value);
+ if (coerced || i == inputs.size() - 1) {
+ return coerced;
+ }
+ }
+
+ assert(false);
+ return EvaluationError { "Unreachable" };
+};
+
+void Coercion::eachChild(const std::function<void(const Expression&)>& visit) const {
+ for(const std::unique_ptr<Expression>& input : inputs) {
+ visit(*input);
+ }
+};
+
+bool Coercion::operator==(const Expression& e) const {
+ if (auto rhs = dynamic_cast<const Coercion*>(&e)) {
+ return getType() == rhs->getType() && Expression::childrenEqual(inputs, rhs->inputs);
+ }
+ return false;
+}
+
+} // namespace expression
+} // namespace style
+} // namespace mbgl
+
+
+
diff --git a/src/mbgl/style/expression/compound_expression.cpp b/src/mbgl/style/expression/compound_expression.cpp
new file mode 100644
index 0000000000..c1e0639562
--- /dev/null
+++ b/src/mbgl/style/expression/compound_expression.cpp
@@ -0,0 +1,571 @@
+#include <mbgl/style/expression/compound_expression.hpp>
+#include <mbgl/style/expression/check_subtype.hpp>
+#include <mbgl/style/expression/util.hpp>
+#include <mbgl/tile/geometry_tile_data.hpp>
+#include <mbgl/util/ignore.hpp>
+
+namespace mbgl {
+namespace style {
+namespace expression {
+
+namespace detail {
+
+/*
+ The Signature<Fn> structs are wrappers around an "evaluate()" function whose
+ purpose is to extract the necessary Type data from the evaluate function's
+ type. There are three key (partial) specializations:
+
+ Signature<R (Params...)>:
+ Wraps a simple evaluate function (const T0&, const T1&, ...) -> Result<U>
+
+ Signature<R (const Varargs<T>&)>:
+ Wraps an evaluate function that takes an arbitrary number of arguments (via
+ a Varargs<T>, which is just an alias for std::vector).
+
+ Signature<R (const EvaluationContext&, Params...)>:
+ Wraps an evaluate function that needs to access the expression evaluation
+ parameters in addition to its subexpressions, i.e.,
+ (const EvaluationParams& const T0&, const T1&, ...) -> Result<U>. Needed
+ for expressions like ["zoom"], ["get", key], etc.
+
+ In each of the above evaluate signatures, T0, T1, etc. are the types of
+ the successfully evaluated subexpressions.
+*/
+template <class, class Enable = void>
+struct Signature;
+
+// Simple evaluate function (const T0&, const T1&, ...) -> Result<U>
+template <class R, class... Params>
+struct Signature<R (Params...)> : SignatureBase {
+ using Args = std::array<std::unique_ptr<Expression>, sizeof...(Params)>;
+
+ Signature(R (*evaluate_)(Params...)) :
+ SignatureBase(
+ valueTypeToExpressionType<std::decay_t<typename R::Value>>(),
+ std::vector<type::Type> {valueTypeToExpressionType<std::decay_t<Params>>()...}
+ ),
+ 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 {
+ 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));
+ }
+
+ R (*evaluate)(Params...);
+private:
+ template <std::size_t ...I>
+ EvaluationResult applyImpl(const EvaluationContext& evaluationParameters, const Args& args, std::index_sequence<I...>) const {
+ const std::array<EvaluationResult, sizeof...(I)> evaluated = {{std::get<I>(args)->evaluate(evaluationParameters)...}};
+ for (const auto& arg : evaluated) {
+ if(!arg) return arg.error();
+ }
+ const R value = evaluate(*fromExpressionValue<std::decay_t<Params>>(*(evaluated[I]))...);
+ if (!value) return value.error();
+ return *value;
+ }
+};
+
+// Varargs evaluate function (const Varargs<T>&) -> Result<U>
+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>&)) :
+ SignatureBase(
+ valueTypeToExpressionType<std::decay_t<typename R::Value>>(),
+ VarargsType { valueTypeToExpressionType<T>() }
+ ),
+ evaluate(evaluate_)
+ {}
+
+ std::unique_ptr<Expression> makeExpression(const std::string& name,
+ std::vector<std::unique_ptr<Expression>> args) const override {
+ return std::make_unique<CompoundExpression<Signature>>(name, *this, std::move(args));
+ };
+
+ EvaluationResult apply(const EvaluationContext& evaluationParameters, const Args& args) const {
+ Varargs<T> evaluated;
+ evaluated.reserve(args.size());
+ for (const auto& arg : args) {
+ const EvaluationResult evaluatedArg = arg->evaluate(evaluationParameters);
+ if(!evaluatedArg) return evaluatedArg.error();
+ evaluated.push_back(*fromExpressionValue<std::decay_t<T>>(*evaluatedArg));
+ }
+ const R value = evaluate(evaluated);
+ if (!value) return value.error();
+ return *value;
+ }
+
+ R (*evaluate)(const Varargs<T>&);
+};
+
+// Evaluate function needing parameter access,
+// (const EvaluationParams&, const T0&, const T1&, ...) -> Result<U>
+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...)) :
+ SignatureBase(
+ valueTypeToExpressionType<std::decay_t<typename R::Value>>(),
+ std::vector<type::Type> {valueTypeToExpressionType<std::decay_t<Params>>()...}
+ ),
+ evaluate(evaluate_)
+ {}
+
+ std::unique_ptr<Expression> makeExpression(const std::string& name,
+ 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));
+ }
+
+ EvaluationResult apply(const EvaluationContext& evaluationParameters, const Args& args) const {
+ return applyImpl(evaluationParameters, args, std::index_sequence_for<Params...>{});
+ }
+
+private:
+ template <std::size_t ...I>
+ EvaluationResult applyImpl(const EvaluationContext& evaluationParameters, const Args& args, std::index_sequence<I...>) const {
+ const std::array<EvaluationResult, sizeof...(I)> evaluated = {{std::get<I>(args)->evaluate(evaluationParameters)...}};
+ for (const auto& arg : evaluated) {
+ if(!arg) return arg.error();
+ }
+ // TODO: assert correct runtime type of each arg value
+ const R value = evaluate(evaluationParameters, *fromExpressionValue<std::decay_t<Params>>(*(evaluated[I]))...);
+ if (!value) return value.error();
+ return *value;
+ }
+
+ R (*evaluate)(const EvaluationContext&, Params...);
+};
+
+// Machinery to pull out function types from class methods, lambdas, etc.
+template <class R, class... Params>
+struct Signature<R (*)(Params...)>
+ : Signature<R (Params...)>
+{ using Signature<R (Params...)>::Signature; };
+
+template <class T, class R, class... Params>
+struct Signature<R (T::*)(Params...) const>
+ : Signature<R (Params...)>
+{ using Signature<R (Params...)>::Signature; };
+
+template <class T, class R, class... Params>
+struct Signature<R (T::*)(Params...)>
+ : Signature<R (Params...)>
+{ using Signature<R (Params...)>::Signature; };
+
+template <class Lambda>
+struct Signature<Lambda, std::enable_if_t<std::is_class<Lambda>::value>>
+ : Signature<decltype(&Lambda::operator())>
+{ using Signature<decltype(&Lambda::operator())>::Signature; };
+
+} // namespace detail
+
+using Definition = CompoundExpressionRegistry::Definition;
+
+template <typename T>
+Result<bool> equal(const T& lhs, const T& rhs) { return lhs == rhs; }
+
+template <typename T>
+Result<bool> notEqual(const T& lhs, const T& rhs) { return lhs != rhs; }
+
+template <typename Fn>
+static std::unique_ptr<detail::SignatureBase> makeSignature(Fn evaluateFunction) {
+ return std::make_unique<detail::Signature<Fn>>(evaluateFunction);
+}
+
+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));
+ };
+
+ define("e", []() -> Result<double> { return 2.718281828459045; });
+ define("pi", []() -> Result<double> { return 3.141592653589793; });
+ define("ln2", []() -> Result<double> { return 0.6931471805599453; });
+
+ define("typeof", [](const Value& v) -> Result<std::string> { return toString(typeOf(v)); });
+
+ define("to-string", [](const Value& value) -> Result<std::string> {
+ return value.match(
+ [](const Color& c) -> Result<std::string> { return c.stringify(); }, // avoid quoting
+ [](const std::string& s) -> Result<std::string> { return s; }, // avoid quoting
+ [](const auto& v) -> Result<std::string> { return stringify(v); }
+ );
+ });
+
+ define("to-boolean", [](const Value& v) -> Result<bool> {
+ return v.match(
+ [&] (double f) { return (bool)f; },
+ [&] (const std::string& s) { return s.length() > 0; },
+ [&] (bool b) { return b; },
+ [&] (const NullValue&) { return false; },
+ [&] (const auto&) { return true; }
+ );
+ });
+ define("to-rgba", [](const Color& color) -> Result<std::array<double, 4>> {
+ return std::array<double, 4> {{ color.r, color.g, color.b, color.a }};
+ });
+
+ define("rgba", rgba);
+ define("rgb", [](double r, double g, double b) { return rgba(r, g, b, 1.0f); });
+
+ define("zoom", [](const EvaluationContext& params) -> Result<double> {
+ if (!params.zoom) {
+ return EvaluationError {
+ "The 'zoom' expression is unavailable in the current evaluation context."
+ };
+ }
+ return *(params.zoom);
+ });
+
+ define("heatmap-density", [](const EvaluationContext& params) -> Result<double> {
+ if (!params.heatmapDensity) {
+ return EvaluationError {
+ "The 'heatmap-density' expression is unavailable in the current evaluation context."
+ };
+ }
+ return *(params.heatmapDensity);
+ });
+
+ define("has", [](const EvaluationContext& params, const std::string& key) -> Result<bool> {
+ if (!params.feature) {
+ return EvaluationError {
+ "Feature data is unavailable in the current evaluation context."
+ };
+ }
+
+ return params.feature->getValue(key) ? true : false;
+ });
+ define("has", [](const std::string& key, const std::unordered_map<std::string, Value>& object) -> Result<bool> {
+ return object.find(key) != object.end();
+ });
+
+ define("get", [](const EvaluationContext& params, const std::string& key) -> Result<Value> {
+ if (!params.feature) {
+ return EvaluationError {
+ "Feature data is unavailable in the current evaluation context."
+ };
+ }
+
+ auto propertyValue = params.feature->getValue(key);
+ if (!propertyValue) {
+ return Null;
+ }
+ return Value(toExpressionValue(*propertyValue));
+ });
+ define("get", [](const std::string& key, const std::unordered_map<std::string, Value>& object) -> Result<Value> {
+ if (object.find(key) == object.end()) {
+ return Null;
+ }
+ return object.at(key);
+ });
+
+ define("length", [](const std::vector<Value>& arr) -> Result<double> {
+ return arr.size();
+ });
+ define("length", [] (const std::string s) -> Result<double> {
+ return s.size();
+ });
+
+ define("properties", [](const EvaluationContext& params) -> Result<std::unordered_map<std::string, Value>> {
+ if (!params.feature) {
+ return EvaluationError {
+ "Feature data is unavailable in the current evaluation context."
+ };
+ }
+ std::unordered_map<std::string, Value> result;
+ const PropertyMap properties = params.feature->getProperties();
+ for (const auto& entry : properties) {
+ result[entry.first] = toExpressionValue(entry.second);
+ }
+ return result;
+ });
+
+ define("geometry-type", [](const EvaluationContext& params) -> Result<std::string> {
+ if (!params.feature) {
+ return EvaluationError {
+ "Feature data is unavailable in the current evaluation context."
+ };
+ }
+
+ auto type = params.feature->getType();
+ if (type == FeatureType::Point) {
+ return "Point";
+ } else if (type == FeatureType::LineString) {
+ return "LineString";
+ } else if (type == FeatureType::Polygon) {
+ return "Polygon";
+ } else {
+ return "Unknown";
+ }
+ });
+
+ define("id", [](const EvaluationContext& params) -> Result<Value> {
+ if (!params.feature) {
+ return EvaluationError {
+ "Feature data is unavailable in the current evaluation context."
+ };
+ }
+
+ auto id = params.feature->getID();
+ if (!id) {
+ return Null;
+ }
+ return id->match(
+ [](const auto& idValue) {
+ return toExpressionValue(mbgl::Value(idValue));
+ }
+ );
+ });
+
+ define("+", [](const Varargs<double>& args) -> Result<double> {
+ double sum = 0.0f;
+ for (auto arg : args) {
+ sum += arg;
+ }
+ return sum;
+ });
+ define("-", [](double a, double b) -> Result<double> { return a - b; });
+ define("-", [](double a) -> Result<double> { return -a; });
+ define("*", [](const Varargs<double>& args) -> Result<double> {
+ double prod = 1.0f;
+ for (auto arg : args) {
+ prod *= arg;
+ }
+ return prod;
+ });
+ define("/", [](double a, double b) -> Result<double> { return a / b; });
+ define("%", [](double a, double b) -> Result<double> { return fmod(a, b); });
+ define("^", [](double a, double b) -> Result<double> { return pow(a, b); });
+ define("sqrt", [](double x) -> Result<double> { return sqrt(x); });
+ define("log10", [](double x) -> Result<double> { return log10(x); });
+ define("ln", [](double x) -> Result<double> { return log(x); });
+ define("log2", [](double x) -> Result<double> { return log2(x); });
+ define("sin", [](double x) -> Result<double> { return sin(x); });
+ define("cos", [](double x) -> Result<double> { return cos(x); });
+ define("tan", [](double x) -> Result<double> { return tan(x); });
+ define("asin", [](double x) -> Result<double> { return asin(x); });
+ define("acos", [](double x) -> Result<double> { return acos(x); });
+ define("atan", [](double x) -> Result<double> { return atan(x); });
+
+ define("min", [](const Varargs<double>& args) -> Result<double> {
+ double result = std::numeric_limits<double>::infinity();
+ for (double arg : args) {
+ result = fmin(arg, result);
+ }
+ return result;
+ });
+ define("max", [](const Varargs<double>& args) -> Result<double> {
+ double result = -std::numeric_limits<double>::infinity();
+ for (double arg : args) {
+ result = fmax(arg, result);
+ }
+ return result;
+ });
+
+ define("==", equal<double>);
+ define("==", equal<const std::string&>);
+ define("==", equal<bool>);
+ define("==", equal<NullValue>);
+
+ define("!=", notEqual<double>);
+ define("!=", notEqual<const std::string&>);
+ define("!=", notEqual<bool>);
+ define("!=", notEqual<NullValue>);
+
+ define(">", [](double lhs, double rhs) -> Result<bool> { return lhs > rhs; });
+ define(">", [](const std::string& lhs, const std::string& rhs) -> Result<bool> { return lhs > rhs; });
+ define(">=", [](double lhs, double rhs) -> Result<bool> { return lhs >= rhs; });
+ define(">=",[](const std::string& lhs, const std::string& rhs) -> Result<bool> { return lhs >= rhs; });
+ define("<", [](double lhs, double rhs) -> Result<bool> { return lhs < rhs; });
+ define("<", [](const std::string& lhs, const std::string& rhs) -> Result<bool> { return lhs < rhs; });
+ define("<=", [](double lhs, double rhs) -> Result<bool> { return lhs <= rhs; });
+ define("<=", [](const std::string& lhs, const std::string& rhs) -> Result<bool> { return lhs <= rhs; });
+
+ define("!", [](bool e) -> Result<bool> { return !e; });
+
+ define("upcase", [](const std::string& input) -> Result<std::string> {
+ std::string s = input;
+ std::transform(s.begin(), s.end(), s.begin(),
+ [](unsigned char c){ return std::toupper(c); });
+ return s;
+ });
+ define("downcase", [](const std::string& input) -> Result<std::string> {
+ std::string s = input;
+ std::transform(s.begin(), s.end(), s.begin(),
+ [](unsigned char c){ return std::tolower(c); });
+ return s;
+ });
+ define("concat", [](const Varargs<std::string>& args) -> Result<std::string> {
+ std::string s;
+ for (const std::string& arg : args) {
+ s += arg;
+ }
+ return s;
+ });
+ define("error", [](const std::string& input) -> Result<type::ErrorType> {
+ return EvaluationError { input };
+ });
+
+ return definitions;
+}
+
+std::unordered_map<std::string, Definition> CompoundExpressionRegistry::definitions = initializeDefinitions();
+
+using namespace mbgl::style::conversion;
+ParseResult parseCompoundExpression(const std::string name, const Convertible& value, ParsingContext& ctx) {
+ assert(isArray(value) && arrayLength(value) > 0);
+
+ auto it = CompoundExpressionRegistry::definitions.find(name);
+ if (it == CompoundExpressionRegistry::definitions.end()) {
+ ctx.error(
+ R"(Unknown expression ")" + name + R"(". If you wanted a literal array, use ["literal", [...]].)",
+ 0
+ );
+ return ParseResult();
+ }
+ const CompoundExpressionRegistry::Definition& definition = it->second;
+
+ auto length = arrayLength(value);
+
+ // Check if we have a single signature with the correct number of
+ // parameters. If so, then use that signature's parameter types for parsing
+ // (and inferring the types of) the arguments.
+ optional<std::size_t> singleMatchingSignature;
+ for (std::size_t j = 0; j < definition.size(); j++) {
+ const std::unique_ptr<detail::SignatureBase>& signature = definition[j];
+ if (
+ signature->params.is<VarargsType>() ||
+ signature->params.get<std::vector<type::Type>>().size() == length - 1
+ ) {
+ if (singleMatchingSignature) {
+ singleMatchingSignature = {};
+ } else {
+ singleMatchingSignature = j;
+ }
+ }
+ }
+
+ // parse subexpressions first
+ std::vector<std::unique_ptr<Expression>> args;
+ args.reserve(length - 1);
+ for (std::size_t i = 1; i < length; i++) {
+ optional<type::Type> expected;
+
+ if (singleMatchingSignature) {
+ expected = definition[*singleMatchingSignature]->params.match(
+ [](const VarargsType& varargs) { return varargs.type; },
+ [&](const std::vector<type::Type>& params_) { return params_[i - 1]; }
+ );
+ }
+
+ auto parsed = ctx.parse(arrayMember(value, i), i, expected);
+ if (!parsed) {
+ return parsed;
+ }
+ args.push_back(std::move(*parsed));
+ }
+ return createCompoundExpression(name, definition, std::move(args), ctx);
+}
+
+
+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);
+}
+
+
+ParseResult createCompoundExpression(const std::string& name,
+ const Definition& definition,
+ std::vector<std::unique_ptr<Expression>> args,
+ ParsingContext& ctx)
+{
+ ParsingContext signatureContext(ctx.getKey());
+
+ for (const std::unique_ptr<detail::SignatureBase>& signature : definition) {
+ signatureContext.clearErrors();
+
+ if (signature->params.is<std::vector<type::Type>>()) {
+ const std::vector<type::Type>& params = signature->params.get<std::vector<type::Type>>();
+ if (params.size() != args.size()) {
+ signatureContext.error(
+ "Expected " + std::to_string(params.size()) +
+ " arguments, but found " + std::to_string(args.size()) + " instead."
+ );
+ continue;
+ }
+
+ for (std::size_t i = 0; i < args.size(); i++) {
+ const std::unique_ptr<Expression>& arg = args[i];
+ optional<std::string> err = type::checkSubtype(params.at(i), arg->getType());
+ if (err) {
+ signatureContext.error(*err, i + 1);
+ }
+ }
+ } else if (signature->params.is<VarargsType>()) {
+ const type::Type& paramType = signature->params.get<VarargsType>().type;
+ for (std::size_t i = 0; i < args.size(); i++) {
+ const std::unique_ptr<Expression>& arg = args[i];
+ optional<std::string> err = type::checkSubtype(paramType, arg->getType());
+ if (err) {
+ signatureContext.error(*err, i + 1);
+ }
+ }
+ }
+
+ if (signatureContext.getErrors().size() == 0) {
+ return ParseResult(signature->makeExpression(name, std::move(args)));
+ }
+ }
+
+ if (definition.size() == 1) {
+ ctx.appendErrors(std::move(signatureContext));
+ } else {
+ std::string signatures;
+ for (const auto& signature : definition) {
+ signatures += (signatures.size() > 0 ? " | " : "");
+ signature->params.match(
+ [&](const VarargsType& varargs) {
+ signatures += "(" + toString(varargs.type) + ")";
+ },
+ [&](const std::vector<type::Type>& params) {
+ signatures += "(";
+ bool first = true;
+ for (const type::Type& param : params) {
+ if (!first) signatures += ", ";
+ signatures += toString(param);
+ first = false;
+ }
+ signatures += ")";
+ }
+ );
+
+ }
+ std::string actualTypes;
+ for (const auto& arg : args) {
+ if (actualTypes.size() > 0) {
+ actualTypes += ", ";
+ }
+ actualTypes += toString(arg->getType());
+ }
+ ctx.error("Expected arguments of type " + signatures + ", but found (" + actualTypes + ") instead.");
+ }
+
+ return ParseResult();
+}
+
+} // namespace expression
+} // namespace style
+} // namespace mbgl
diff --git a/src/mbgl/style/expression/find_zoom_curve.cpp b/src/mbgl/style/expression/find_zoom_curve.cpp
new file mode 100644
index 0000000000..5d39e0791e
--- /dev/null
+++ b/src/mbgl/style/expression/find_zoom_curve.cpp
@@ -0,0 +1,76 @@
+#include <mbgl/style/expression/find_zoom_curve.hpp>
+#include <mbgl/style/expression/compound_expression.hpp>
+#include <mbgl/style/expression/let.hpp>
+#include <mbgl/style/expression/coalesce.hpp>
+
+#include <mbgl/util/variant.hpp>
+#include <mbgl/util/optional.hpp>
+
+namespace mbgl {
+namespace style {
+namespace expression {
+
+optional<variant<const InterpolateBase*, const Step*, ParsingError>> findZoomCurve(const expression::Expression* e) {
+ optional<variant<const InterpolateBase*, const Step*, ParsingError>> result;
+
+ if (auto let = dynamic_cast<const Let*>(e)) {
+ result = findZoomCurve(let->getResult());
+ } else if (auto coalesce = dynamic_cast<const Coalesce*>(e)) {
+ std::size_t length = coalesce->getLength();
+ for (std::size_t i = 0; i < length; i++) {
+ result = findZoomCurve(coalesce->getChild(i));
+ if (result) {
+ break;
+ }
+ }
+ } else if (auto curve = dynamic_cast<const InterpolateBase*>(e)) {
+ auto z = dynamic_cast<CompoundExpressionBase*>(curve->getInput().get());
+ if (z && z->getName() == "zoom") {
+ result = {curve};
+ }
+ } else if (auto step = dynamic_cast<const Step*>(e)) {
+ auto z = dynamic_cast<CompoundExpressionBase*>(step->getInput().get());
+ if (z && z->getName() == "zoom") {
+ result = {step};
+ }
+ }
+
+ if (result && result->is<ParsingError>()) {
+ return result;
+ }
+
+ e->eachChild([&](const Expression& child) {
+ optional<variant<const InterpolateBase*, const Step*, ParsingError>> childResult(findZoomCurve(&child));
+ if (childResult) {
+ if (childResult->is<ParsingError>()) {
+ result = childResult;
+ } else if (!result && childResult) {
+ result = {ParsingError {
+ R"("zoom" expression may only be used as input to a top-level "step" or "interpolate" expression.)", ""
+ }};
+ } else if (result && childResult && result != childResult) {
+ result = {ParsingError {
+ R"(Only one zoom-based "step" or "interpolate" subexpression may be used in an expression.)", ""
+ }};
+ }
+ }
+ });
+
+ return result;
+}
+
+variant<const InterpolateBase*, const Step*> findZoomCurveChecked(const expression::Expression* e) {
+ return findZoomCurve(e)->match(
+ [](const ParsingError&) -> variant<const InterpolateBase*, const Step*> {
+ assert(false);
+ return {};
+ },
+ [](auto zoomCurve) -> variant<const InterpolateBase*, const Step*> {
+ return {std::move(zoomCurve)};
+ }
+ );
+}
+
+} // namespace expression
+} // namespace style
+} // namespace mbgl
diff --git a/src/mbgl/style/expression/get_covering_stops.cpp b/src/mbgl/style/expression/get_covering_stops.cpp
new file mode 100644
index 0000000000..c9f87d93ac
--- /dev/null
+++ b/src/mbgl/style/expression/get_covering_stops.cpp
@@ -0,0 +1,26 @@
+#include <mbgl/style/expression/get_covering_stops.hpp>
+
+namespace mbgl {
+namespace style {
+namespace expression {
+
+Range<float> getCoveringStops(const std::map<double, std::unique_ptr<Expression>>& stops,
+ const double lower, const double upper) {
+ assert(!stops.empty());
+ auto minIt = stops.lower_bound(lower);
+ auto maxIt = stops.lower_bound(upper);
+
+ // lower_bound yields first element >= lowerZoom, but we want the *last*
+ // element <= lowerZoom, so if we found a stop > lowerZoom, back up by one.
+ if (minIt != stops.begin() && minIt != stops.end() && minIt->first > lower) {
+ minIt--;
+ }
+ return Range<float> {
+ static_cast<float>(minIt == stops.end() ? stops.rbegin()->first : minIt->first),
+ static_cast<float>(maxIt == stops.end() ? stops.rbegin()->first : maxIt->first)
+ };
+}
+
+} // namespace expression
+} // namespace style
+} // namespace mbgl
diff --git a/src/mbgl/style/expression/interpolate.cpp b/src/mbgl/style/expression/interpolate.cpp
new file mode 100644
index 0000000000..020aba9dce
--- /dev/null
+++ b/src/mbgl/style/expression/interpolate.cpp
@@ -0,0 +1,211 @@
+
+#include <mbgl/style/expression/interpolate.hpp>
+
+namespace mbgl {
+namespace style {
+namespace expression {
+
+using Interpolator = variant<ExponentialInterpolator,
+ CubicBezierInterpolator>;
+
+using namespace mbgl::style::conversion;
+
+ParseResult parseInterpolate(const Convertible& value, ParsingContext& ctx) {
+ assert(isArray(value));
+
+ auto length = arrayLength(value);
+
+ if (length < 2) {
+ ctx.error("Expected an interpolation type expression.");
+ return ParseResult();
+ }
+
+ const Convertible& interp = arrayMember(value, 1);
+ if (!isArray(interp) || arrayLength(interp) == 0) {
+ ctx.error("Expected an interpolation type expression.");
+ return ParseResult();
+ }
+
+ optional<Interpolator> interpolator;
+
+ const optional<std::string> interpName = toString(arrayMember(interp, 0));
+ if (interpName && *interpName == "linear") {
+ interpolator = {ExponentialInterpolator(1.0)};
+ } else if (interpName && *interpName == "exponential") {
+ optional<double> base;
+ if (arrayLength(interp) == 2) {
+ base = toDouble(arrayMember(interp, 1));
+ }
+ if (!base) {
+ ctx.error("Exponential interpolation requires a numeric base.", 1, 1);
+ return ParseResult();
+ }
+ interpolator = {ExponentialInterpolator(*base)};
+ } else if (interpName && *interpName == "cubic-bezier") {
+ optional<double> x1;
+ optional<double> y1;
+ optional<double> x2;
+ optional<double> y2;
+ if (arrayLength(interp) == 5) {
+ x1 = toDouble(arrayMember(interp, 1));
+ y1 = toDouble(arrayMember(interp, 2));
+ x2 = toDouble(arrayMember(interp, 3));
+ y2 = toDouble(arrayMember(interp, 4));
+ }
+ if (
+ !x1 || !y1 || !x2 || !y2 ||
+ *x1 < 0 || *x1 > 1 ||
+ *y1 < 0 || *y1 > 1 ||
+ *x2 < 0 || *x2 > 1 ||
+ *y2 < 0 || *y2 > 1
+ ) {
+ ctx.error("Cubic bezier interpolation requires four numeric arguments with values between 0 and 1.", 1);
+ return ParseResult();
+
+ }
+ interpolator = {CubicBezierInterpolator(*x1, *y1, *x2, *y2)};
+ }
+
+ if (!interpolator) {
+ ctx.error("Unknown interpolation type " + (interpName ? *interpName : ""), 1, 0);
+ return ParseResult();
+ }
+
+ std::size_t minArgs = 4;
+ if (length - 1 < minArgs) {
+ ctx.error("Expected at least 4 arguments, but found only " + std::to_string(length - 1) + ".");
+ return ParseResult();
+ }
+
+ // [interpolation, interp_type, input, 2 * (n pairs)...]
+ if ((length - 1) % 2 != 0) {
+ ctx.error("Expected an even number of arguments.");
+ return ParseResult();
+ }
+
+ ParseResult input = ctx.parse(arrayMember(value, 2), 2, {type::Number});
+ if (!input) {
+ return input;
+ }
+
+ std::map<double, std::unique_ptr<Expression>> stops;
+ optional<type::Type> outputType;
+ if (ctx.getExpected() && *ctx.getExpected() != type::Value) {
+ outputType = ctx.getExpected();
+ }
+
+ double previous = - std::numeric_limits<double>::infinity();
+
+ for (std::size_t i = 3; i + 1 < length; i += 2) {
+ const optional<mbgl::Value> labelValue = toValue(arrayMember(value, i));
+ optional<double> label;
+ optional<std::string> labelError;
+ if (labelValue) {
+ labelValue->match(
+ [&](uint64_t n) {
+ if (n > std::numeric_limits<double>::max()) {
+ label = {std::numeric_limits<double>::infinity()};
+ } else {
+ label = {static_cast<double>(n)};
+ }
+ },
+ [&](int64_t n) {
+ if (n > std::numeric_limits<double>::max()) {
+ label = {std::numeric_limits<double>::infinity()};
+ } else {
+ label = {static_cast<double>(n)};
+ }
+ },
+ [&](double n) {
+ if (n > std::numeric_limits<double>::max()) {
+ label = {std::numeric_limits<double>::infinity()};
+ } else {
+ label = {static_cast<double>(n)};
+ }
+ },
+ [&](const auto&) {}
+ );
+ }
+ if (!label) {
+ ctx.error(labelError ? *labelError :
+ R"(Input/output pairs for "interpolate" expressions must be defined using literal numeric values (not computed expressions) for the input values.)",
+ i);
+ return ParseResult();
+ }
+
+ if (*label <= previous) {
+ ctx.error(
+ R"(Input/output pairs for "interpolate" expressions must be arranged with input values in strictly ascending order.)",
+ i
+ );
+ return ParseResult();
+ }
+ previous = *label;
+
+ auto output = ctx.parse(arrayMember(value, i + 1), i + 1, outputType);
+ if (!output) {
+ return ParseResult();
+ }
+ if (!outputType) {
+ outputType = (*output)->getType();
+ }
+
+ stops.emplace(*label, std::move(*output));
+ }
+
+ assert(outputType);
+
+ if (
+ *outputType != type::Number &&
+ *outputType != type::Color &&
+ !(
+ outputType->is<type::Array>() &&
+ outputType->get<type::Array>().itemType == type::Number &&
+ outputType->get<type::Array>().N
+ )
+ )
+ {
+ ctx.error("Type " + toString(*outputType) + " is not interpolatable.");
+ return ParseResult();
+ }
+
+ return outputType->match(
+ [&](const type::NumberType&) -> ParseResult {
+ return interpolator->match([&](const auto& interpolator_) {
+ return ParseResult(std::make_unique<Interpolate<double>>(
+ *outputType, interpolator_, std::move(*input), std::move(stops)
+ ));
+ });
+ },
+ [&](const type::ColorType&) -> ParseResult {
+ return interpolator->match([&](const auto& interpolator_) {
+ return ParseResult(std::make_unique<Interpolate<Color>>(
+ *outputType, interpolator_, std::move(*input), std::move(stops)
+ ));
+ });
+ },
+ [&](const type::Array& arrayType) -> ParseResult {
+ return interpolator->match(
+ [&](const auto& continuousInterpolator) {
+ if (arrayType.itemType != type::Number || !arrayType.N) {
+ assert(false); // interpolability already checked above.
+ return ParseResult();
+ }
+ return ParseResult(std::make_unique<Interpolate<std::vector<Value>>>(
+ *outputType, continuousInterpolator, std::move(*input), std::move(stops)
+ ));
+ }
+ );
+ },
+ [&](const auto&) {
+ // unreachable: Null, Boolean, String, Object, Value output types
+ // are not interpolatable, and interpolability was already checked above
+ assert(false);
+ return ParseResult();
+ }
+ );
+}
+
+} // namespace expression
+} // namespace style
+} // namespace mbgl
diff --git a/src/mbgl/style/expression/is_constant.cpp b/src/mbgl/style/expression/is_constant.cpp
new file mode 100644
index 0000000000..0ebb37faa9
--- /dev/null
+++ b/src/mbgl/style/expression/is_constant.cpp
@@ -0,0 +1,40 @@
+#include <mbgl/style/expression/is_constant.hpp>
+
+namespace mbgl {
+namespace style {
+namespace expression {
+
+bool isFeatureConstant(const Expression& expression) {
+ if (auto e = dynamic_cast<const CompoundExpressionBase*>(&expression)) {
+ const std::string name = e->getName();
+ optional<std::size_t> parameterCount = e->getParameterCount();
+ if (name == "get" && parameterCount && *parameterCount == 1) {
+ return false;
+ } else if (name == "has" && parameterCount && *parameterCount == 1) {
+ return false;
+ } else if (
+ name == "properties" ||
+ name == "geometry-type" ||
+ name == "id"
+ ) {
+ return false;
+ }
+ }
+
+ bool featureConstant = true;
+ expression.eachChild([&](const Expression& e) {
+ if (featureConstant && !isFeatureConstant(e)) {
+ featureConstant = false;
+ }
+ });
+ return featureConstant;
+}
+
+bool isZoomConstant(const Expression& e) {
+ return isGlobalPropertyConstant(e, std::array<std::string, 1>{{"zoom"}});
+}
+
+
+} // namespace expression
+} // namespace style
+} // namespace mbgl
diff --git a/src/mbgl/style/expression/is_expression.cpp b/src/mbgl/style/expression/is_expression.cpp
new file mode 100644
index 0000000000..77212f6a1e
--- /dev/null
+++ b/src/mbgl/style/expression/is_expression.cpp
@@ -0,0 +1,29 @@
+#include <mbgl/style/expression/is_expression.hpp>
+#include <mbgl/style/expression/compound_expression.hpp>
+#include <mbgl/style/expression/parsing_context.hpp>
+
+#include <mbgl/style/conversion.hpp>
+
+#include <unordered_set>
+
+namespace mbgl {
+namespace style {
+namespace expression {
+
+using namespace mbgl::style::conversion;
+
+bool isExpression(const Convertible& value) {
+ const ExpressionRegistry& registry = getExpressionRegistry();
+
+ if (!isArray(value) || arrayLength(value) == 0) return false;
+ optional<std::string> name = toString(arrayMember(value, 0));
+ if (!name) return false;
+
+ return (registry.find(*name) != registry.end()) ||
+ (CompoundExpressionRegistry::definitions.find(*name) != CompoundExpressionRegistry::definitions.end());
+}
+
+
+} // namespace expression
+} // namespace style
+} // namespace mbgl
diff --git a/src/mbgl/style/expression/let.cpp b/src/mbgl/style/expression/let.cpp
new file mode 100644
index 0000000000..8e206d3582
--- /dev/null
+++ b/src/mbgl/style/expression/let.cpp
@@ -0,0 +1,91 @@
+#include <mbgl/style/expression/let.hpp>
+#include <mbgl/style/conversion/get_json_type.hpp>
+
+namespace mbgl {
+namespace style {
+namespace expression {
+
+EvaluationResult Let::evaluate(const EvaluationContext& params) const {
+ return result->evaluate(params);
+}
+
+void Let::eachChild(const std::function<void(const Expression&)>& visit) const {
+ for (auto it = bindings.begin(); it != bindings.end(); it++) {
+ visit(*it->second);
+ }
+ visit(*result);
+}
+
+using namespace mbgl::style::conversion;
+
+ParseResult Let::parse(const Convertible& value, ParsingContext& ctx) {
+ assert(isArray(value));
+
+ std::size_t length = arrayLength(value);
+
+ if (length < 4) {
+ ctx.error("Expected at least 3 arguments, but found " + std::to_string(length - 1) + " instead.");
+ return ParseResult();
+ }
+
+ std::map<std::string, std::shared_ptr<Expression>> bindings_;
+ for(std::size_t i = 1; i < length - 1; i += 2) {
+ optional<std::string> name = toString(arrayMember(value, i));
+ if (!name) {
+ ctx.error("Expected string, but found " + getJSONType(arrayMember(value, i)) + " instead.", i);
+ return ParseResult();
+ }
+
+ bool isValidName = std::all_of(name->begin(), name->end(), [](unsigned char c) {
+ return std::isalnum(c) || c == '_';
+ });
+ if (!isValidName) {
+ ctx.error("Variable names must contain only alphanumeric characters or '_'.", 1);
+ return ParseResult();
+ }
+
+ ParseResult bindingValue = ctx.parse(arrayMember(value, i + 1), i + 1);
+ if (!bindingValue) {
+ return ParseResult();
+ }
+
+ bindings_.emplace(*name, std::move(*bindingValue));
+ }
+
+ ParseResult result_ = ctx.parse(arrayMember(value, length - 1), length - 1, ctx.getExpected(), bindings_);
+ if (!result_) {
+ return ParseResult();
+ }
+
+ return ParseResult(std::make_unique<Let>(std::move(bindings_), std::move(*result_)));
+}
+
+EvaluationResult Var::evaluate(const EvaluationContext& params) const {
+ return value->evaluate(params);
+}
+
+void Var::eachChild(const std::function<void(const Expression&)>&) const {}
+
+ParseResult Var::parse(const Convertible& value_, ParsingContext& ctx) {
+ assert(isArray(value_));
+
+ if (arrayLength(value_) != 2 || !toString(arrayMember(value_, 1))) {
+ ctx.error("'var' expression requires exactly one string literal argument.");
+ return ParseResult();
+ }
+
+ std::string name_ = *toString(arrayMember(value_, 1));
+
+ optional<std::shared_ptr<Expression>> bindingValue = ctx.getBinding(name_);
+ if (!bindingValue) {
+ ctx.error(R"(Unknown variable ")" + name_ + R"(". Make sure ")" +
+ name_ + R"(" has been bound in an enclosing "let" expression before using it.)", 1);
+ return ParseResult();
+ }
+
+ return ParseResult(std::make_unique<Var>(name_, std::move(*bindingValue)));
+}
+
+} // namespace expression
+} // namespace style
+} // namespace mbgl
diff --git a/src/mbgl/style/expression/literal.cpp b/src/mbgl/style/expression/literal.cpp
new file mode 100644
index 0000000000..fc11878bea
--- /dev/null
+++ b/src/mbgl/style/expression/literal.cpp
@@ -0,0 +1,108 @@
+
+#include <mbgl/style/expression/literal.hpp>
+
+namespace mbgl {
+namespace style {
+namespace expression {
+
+template <typename T>
+optional<Value> checkNumber(T n) {
+ if (n > std::numeric_limits<double>::max()) {
+ return {std::numeric_limits<double>::infinity()};
+ } else {
+ return {static_cast<double>(n)};
+ }
+}
+
+using namespace mbgl::style::conversion;
+optional<Value> parseValue(const Convertible& value, ParsingContext& ctx) {
+ if (isUndefined(value)) return {Null};
+ if (isObject(value)) {
+ std::unordered_map<std::string, Value> result;
+ bool error = false;
+ eachMember(value, [&] (const std::string& k, const mbgl::style::conversion::Convertible& v) -> optional<conversion::Error> {
+ if (!error) {
+ optional<Value> memberValue = parseValue(v, ctx);
+ if (memberValue) {
+ result.emplace(k, *memberValue);
+ } else {
+ error = true;
+ }
+ }
+ return {};
+ });
+ return error ? optional<Value>() : optional<Value>(result);
+ }
+
+ if (isArray(value)) {
+ std::vector<Value> result;
+ const auto length = arrayLength(value);
+ for(std::size_t i = 0; i < length; i++) {
+ optional<Value> item = parseValue(arrayMember(value, i), ctx);
+ if (item) {
+ result.emplace_back(*item);
+ } else {
+ return optional<Value>();
+ }
+ }
+ return optional<Value>(result);
+ }
+
+ optional<mbgl::Value> v = toValue(value);
+ // since value represents a JSON value, if it's not undefined, object, or
+ // array, it must be convertible to mbgl::Value
+ assert(v);
+
+ return v->match(
+ [&](uint64_t n) { return checkNumber(n); },
+ [&](int64_t n) { return checkNumber(n); },
+ [&](double n) { return checkNumber(n); },
+ [&](const auto&) {
+ return optional<Value>(toExpressionValue(*v));
+ }
+ );
+}
+
+ParseResult Literal::parse(const Convertible& value, ParsingContext& ctx) {
+ if (isObject(value)) {
+ ctx.error(R"(Bare objects invalid. Use ["literal", {...}] instead.)");
+ return ParseResult();
+ } else if (isArray(value)) {
+ // object or array value, quoted with ["literal", value]
+ if (arrayLength(value) != 2) {
+ ctx.error("'literal' expression requires exactly one argument, but found " + std::to_string(arrayLength(value) - 1) + " instead.");
+ return ParseResult();
+ }
+ const optional<Value> parsedValue = parseValue(arrayMember(value, 1), ctx);
+ if (!parsedValue) {
+ return ParseResult();
+ }
+
+ // special case: infer the item type if possible for zero-length arrays
+ if (
+ ctx.getExpected() &&
+ ctx.getExpected()->template is<type::Array>() &&
+ parsedValue->template is<std::vector<Value>>()
+ ) {
+ auto type = typeOf(*parsedValue).template get<type::Array>();
+ auto expected = ctx.getExpected()->template get<type::Array>();
+ if (
+ type.N && (*type.N == 0) &&
+ (!expected.N || (*expected.N == 0))
+ ) {
+ return ParseResult(std::make_unique<Literal>(expected, parsedValue->template get<std::vector<Value>>()));
+ }
+ }
+
+ return ParseResult(std::make_unique<Literal>(*parsedValue));
+ } else {
+ // bare primitive value (string, number, boolean, null)
+ const optional<Value> parsedValue = parseValue(value, ctx);
+ return ParseResult(std::make_unique<Literal>(*parsedValue));
+ }
+}
+
+} // namespace expression
+} // namespace style
+} // namespace mbgl
+
diff --git a/src/mbgl/style/expression/match.cpp b/src/mbgl/style/expression/match.cpp
new file mode 100644
index 0000000000..6336eba1e6
--- /dev/null
+++ b/src/mbgl/style/expression/match.cpp
@@ -0,0 +1,262 @@
+#include <mbgl/style/expression/match.hpp>
+#include <mbgl/style/expression/check_subtype.hpp>
+#include <mbgl/style/expression/parsing_context.hpp>
+
+namespace mbgl {
+namespace style {
+namespace expression {
+
+template <typename T>
+void Match<T>::eachChild(const std::function<void(const Expression&)>& visit) const {
+ visit(*input);
+ for (const std::pair<T, std::shared_ptr<Expression>>& branch : branches) {
+ visit(*branch.second);
+ }
+ visit(*otherwise);
+}
+
+template <typename T>
+bool Match<T>::operator==(const Expression& e) const {
+ if (auto rhs = dynamic_cast<const Match*>(&e)) {
+ return (*input == *(rhs->input) &&
+ *otherwise == *(rhs->otherwise) &&
+ Expression::childrenEqual(branches, rhs->branches));
+ }
+ return false;
+}
+
+
+template<> EvaluationResult Match<std::string>::evaluate(const EvaluationContext& params) const {
+ const EvaluationResult inputValue = input->evaluate(params);
+ if (!inputValue) {
+ return inputValue.error();
+ }
+
+ auto it = branches.find(inputValue->get<std::string>());
+ if (it != branches.end()) {
+ return (*it).second->evaluate(params);
+ }
+
+ return otherwise->evaluate(params);
+}
+
+template<> EvaluationResult Match<int64_t>::evaluate(const EvaluationContext& params) const {
+ const EvaluationResult inputValue = input->evaluate(params);
+ if (!inputValue) {
+ return inputValue.error();
+ }
+
+ const auto numeric = inputValue->get<double>();
+ int64_t rounded = std::floor(numeric);
+ if (numeric == rounded) {
+ auto it = branches.find(rounded);
+ if (it != branches.end()) {
+ return (*it).second->evaluate(params);
+ }
+ }
+
+ return otherwise->evaluate(params);
+}
+
+template class Match<int64_t>;
+template class Match<std::string>;
+
+using InputType = variant<int64_t, std::string>;
+
+using namespace mbgl::style::conversion;
+optional<InputType> parseInputValue(const Convertible& input, ParsingContext& parentContext, std::size_t index, optional<type::Type>& inputType) {
+ using namespace mbgl::style::conversion;
+ optional<InputType> result;
+ optional<type::Type> type;
+
+ auto value = toValue(input);
+
+ if (value) {
+ value->match(
+ [&] (uint64_t n) {
+ if (!Value::isSafeInteger(n)) {
+ parentContext.error("Branch labels must be integers no larger than " + std::to_string(Value::maxSafeInteger()) + ".", index);
+ } else {
+ type = {type::Number};
+ result = {static_cast<int64_t>(n)};
+ }
+ },
+ [&] (int64_t n) {
+ if (!Value::isSafeInteger(n)) {
+ parentContext.error("Branch labels must be integers no larger than " + std::to_string(Value::maxSafeInteger()) + ".", index);
+ } else {
+ type = {type::Number};
+ result = {n};
+ }
+ },
+ [&] (double n) {
+ if (!Value::isSafeInteger(n)) {
+ parentContext.error("Branch labels must be integers no larger than " + std::to_string(Value::maxSafeInteger()) + ".", index);
+ } else if (n != std::floor(n)) {
+ parentContext.error("Numeric branch labels must be integer values.", index);
+ } else {
+ type = {type::Number};
+ result = {static_cast<int64_t>(n)};
+ }
+ },
+ [&] (const std::string& s) {
+ type = {type::String};
+ result = {s};
+ },
+ [&] (const auto&) {
+ parentContext.error("Branch labels must be numbers or strings.", index);
+ }
+ );
+ } else {
+ parentContext.error("Branch labels must be numbers or strings.", index);
+ }
+
+ if (!type) {
+ return result;
+ }
+
+ if (!inputType) {
+ inputType = type;
+ } else {
+ optional<std::string> err = type::checkSubtype(*inputType, *type);
+ if (err) {
+ parentContext.error(*err, index);
+ return optional<InputType>();
+ }
+ }
+
+ return result;
+}
+
+template <typename T>
+static ParseResult create(type::Type outputType,
+ std::unique_ptr<Expression>input,
+ std::vector<std::pair<std::vector<InputType>,
+ std::unique_ptr<Expression>>> branches,
+ std::unique_ptr<Expression> otherwise,
+ ParsingContext& ctx) {
+ typename Match<T>::Branches typedBranches;
+
+ std::size_t index = 2;
+
+ typedBranches.reserve(branches.size());
+ for (std::pair<std::vector<InputType>,
+ std::unique_ptr<Expression>>& pair : branches) {
+ std::shared_ptr<Expression> result = std::move(pair.second);
+ for (const InputType& label : pair.first) {
+ const auto& typedLabel = label.template get<T>();
+ if (typedBranches.find(typedLabel) != typedBranches.end()) {
+ ctx.error("Branch labels must be unique.", index);
+ return ParseResult();
+ }
+ typedBranches.emplace(typedLabel, result);
+ }
+
+ index += 2;
+ }
+ return ParseResult(std::make_unique<Match<T>>(
+ outputType,
+ std::move(input),
+ std::move(typedBranches),
+ std::move(otherwise)
+ ));
+}
+
+ParseResult parseMatch(const Convertible& value, ParsingContext& ctx) {
+ assert(isArray(value));
+ auto length = arrayLength(value);
+ if (length < 5) {
+ ctx.error(
+ "Expected at least 4 arguments, but found only " + std::to_string(length - 1) + "."
+ );
+ return ParseResult();
+ }
+
+ // Expect odd-length array: ["match", input, 2 * (n pairs)..., otherwise]
+ if (length % 2 != 1) {
+ ctx.error("Expected an even number of arguments.");
+ return ParseResult();
+ }
+
+ optional<type::Type> inputType;
+ optional<type::Type> outputType;
+ if (ctx.getExpected() && *ctx.getExpected() != type::Value) {
+ outputType = ctx.getExpected();
+ }
+
+ std::vector<std::pair<std::vector<InputType>,
+ std::unique_ptr<Expression>>> branches;
+
+ branches.reserve((length - 3) / 2);
+ for (size_t i = 2; i + 1 < length; i += 2) {
+ const auto& label = arrayMember(value, i);
+
+ std::vector<InputType> labels;
+ // Match pair inputs are provided as either a literal value or a
+ // raw JSON array of string / number / boolean values.
+ if (isArray(label)) {
+ auto groupLength = arrayLength(label);
+ if (groupLength == 0) {
+ ctx.error("Expected at least one branch label.", i);
+ return ParseResult();
+ }
+
+ labels.reserve(groupLength);
+ for (size_t j = 0; j < groupLength; j++) {
+ const optional<InputType> inputValue = parseInputValue(arrayMember(label, j), ctx, i, inputType);
+ if (!inputValue) {
+ return ParseResult();
+ }
+ labels.push_back(*inputValue);
+ }
+ } else {
+ const optional<InputType> inputValue = parseInputValue(label, ctx, i, inputType);
+ if (!inputValue) {
+ return ParseResult();
+ }
+ labels.push_back(*inputValue);
+ }
+
+ ParseResult output = ctx.parse(arrayMember(value, i + 1), i + 1, outputType);
+ if (!output) {
+ return ParseResult();
+ }
+
+ if (!outputType) {
+ outputType = (*output)->getType();
+ }
+
+ branches.push_back(std::make_pair(std::move(labels), std::move(*output)));
+ }
+
+ auto input = ctx.parse(arrayMember(value, 1), 1, inputType);
+ if (!input) {
+ return ParseResult();
+ }
+
+ auto otherwise = ctx.parse(arrayMember(value, length - 1), length - 1, outputType);
+ if (!otherwise) {
+ return ParseResult();
+ }
+
+ assert(inputType && outputType);
+
+ return inputType->match(
+ [&](const type::NumberType&) {
+ return create<int64_t>(*outputType, std::move(*input), std::move(branches), std::move(*otherwise), ctx);
+ },
+ [&](const type::StringType&) {
+ return create<std::string>(*outputType, std::move(*input), std::move(branches), std::move(*otherwise), ctx);
+ },
+ [&](const auto&) {
+ // unreachable: inputType is set by parseInputValue(), which only
+ // accepts string and (integer) numeric values.
+ assert(false);
+ return ParseResult();
+ }
+ );
+}
+
+} // namespace expression
+} // namespace style
+} // namespace mbgl
diff --git a/src/mbgl/style/expression/parsing_context.cpp b/src/mbgl/style/expression/parsing_context.cpp
new file mode 100644
index 0000000000..81cbdede59
--- /dev/null
+++ b/src/mbgl/style/expression/parsing_context.cpp
@@ -0,0 +1,206 @@
+
+#include <mbgl/style/expression/parsing_context.hpp>
+#include <mbgl/style/expression/check_subtype.hpp>
+#include <mbgl/style/expression/is_constant.hpp>
+#include <mbgl/style/expression/type.hpp>
+
+#include <mbgl/style/expression/expression.hpp>
+#include <mbgl/style/expression/at.hpp>
+#include <mbgl/style/expression/array_assertion.hpp>
+#include <mbgl/style/expression/assertion.hpp>
+#include <mbgl/style/expression/boolean_operator.hpp>
+#include <mbgl/style/expression/case.hpp>
+#include <mbgl/style/expression/coalesce.hpp>
+#include <mbgl/style/expression/coercion.hpp>
+#include <mbgl/style/expression/compound_expression.hpp>
+#include <mbgl/style/expression/interpolate.hpp>
+#include <mbgl/style/expression/let.hpp>
+#include <mbgl/style/expression/literal.hpp>
+#include <mbgl/style/expression/match.hpp>
+#include <mbgl/style/expression/step.hpp>
+
+#include <mbgl/style/expression/find_zoom_curve.hpp>
+
+#include <mbgl/style/conversion/get_json_type.hpp>
+
+namespace mbgl {
+namespace style {
+namespace expression {
+
+bool isConstant(const Expression& expression) {
+ if (dynamic_cast<const Var*>(&expression)) {
+ return false;
+ }
+
+ if (auto compound = dynamic_cast<const CompoundExpressionBase*>(&expression)) {
+ if (compound->getName() == "error") {
+ return false;
+ }
+ }
+
+ bool literalArgs = true;
+ expression.eachChild([&](const Expression& child) {
+ if (!dynamic_cast<const Literal*>(&child)) {
+ literalArgs = false;
+ }
+ });
+ if (!literalArgs) {
+ return false;
+ }
+
+ return isFeatureConstant(expression) &&
+ isGlobalPropertyConstant(expression, std::array<std::string, 2>{{"zoom", "heatmap-density"}});
+}
+
+using namespace mbgl::style::conversion;
+
+ParseResult ParsingContext::parse(const Convertible& value, std::size_t index_, optional<type::Type> expected_) {
+ ParsingContext child(key + "[" + std::to_string(index_) + "]",
+ errors,
+ std::move(expected_),
+ scope);
+ return child.parse(value);
+}
+
+ParseResult ParsingContext::parse(const Convertible& value, std::size_t index_, optional<type::Type> expected_,
+ const std::map<std::string, std::shared_ptr<Expression>>& bindings) {
+ ParsingContext child(key + "[" + std::to_string(index_) + "]",
+ errors,
+ std::move(expected_),
+ std::make_shared<detail::Scope>(bindings, scope));
+ return child.parse(value);
+}
+
+const ExpressionRegistry& getExpressionRegistry() {
+ static ExpressionRegistry registry {{
+ {"all", All::parse},
+ {"any", Any::parse},
+ {"array", ArrayAssertion::parse},
+ {"at", At::parse},
+ {"boolean", Assertion::parse},
+ {"case", Case::parse},
+ {"coalesce", Coalesce::parse},
+ {"interpolate", parseInterpolate},
+ {"let", Let::parse},
+ {"literal", Literal::parse},
+ {"match", parseMatch},
+ {"number", Assertion::parse},
+ {"object", Assertion::parse},
+ {"step", Step::parse},
+ {"string", Assertion::parse},
+ {"to-color", Coercion::parse},
+ {"to-number", Coercion::parse},
+ {"var", Var::parse}
+ }};
+ return registry;
+}
+
+ParseResult ParsingContext::parse(const Convertible& value)
+{
+ ParseResult parsed;
+
+ if (isArray(value)) {
+ const std::size_t length = arrayLength(value);
+ if (length == 0) {
+ error(R"(Expected an array with at least one element. If you wanted a literal array, use ["literal", []].)");
+ return ParseResult();
+ }
+
+ const optional<std::string> op = toString(arrayMember(value, 0));
+ if (!op) {
+ error(
+ "Expression name must be a string, but found " + getJSONType(arrayMember(value, 0)) +
+ R"( instead. If you wanted a literal array, use ["literal", [...]].)",
+ 0
+ );
+ return ParseResult();
+ }
+
+ const ExpressionRegistry& registry = getExpressionRegistry();
+ auto parseFunction = registry.find(*op);
+ if (parseFunction != registry.end()) {
+ parsed = parseFunction->second(value, *this);
+ } else {
+ parsed = parseCompoundExpression(*op, value, *this);
+ }
+ } else {
+ parsed = Literal::parse(value, *this);
+ }
+
+ if (!parsed) {
+ assert(errors->size() > 0);
+ } else if (expected) {
+ auto wrapForType = [&](const type::Type& target, std::unique_ptr<Expression> expression) -> std::unique_ptr<Expression> {
+ std::vector<std::unique_ptr<Expression>> args;
+ args.push_back(std::move(expression));
+ if (target == type::Color) {
+ return std::make_unique<Coercion>(target, std::move(args));
+ } else {
+ return std::make_unique<Assertion>(target, std::move(args));
+ }
+ };
+
+ const type::Type actual = (*parsed)->getType();
+ if (*expected == type::Color && (actual == type::String || actual == type::Value)) {
+ parsed = wrapForType(type::Color, std::move(*parsed));
+ } else if ((*expected == type::String || *expected == type::Number || *expected == type::Boolean) && actual == type::Value) {
+ parsed = wrapForType(*expected, std::move(*parsed));
+ }
+
+ checkType((*parsed)->getType());
+ if (errors->size() > 0) {
+ return ParseResult();
+ }
+ }
+
+ // If an expression's arguments are all literals, we can evaluate
+ // it immediately and replace it with a literal value in the
+ // parsed result.
+ if (parsed && !dynamic_cast<Literal *>(parsed->get()) && isConstant(**parsed)) {
+ EvaluationContext params(nullptr);
+ EvaluationResult evaluated((*parsed)->evaluate(params));
+ if (!evaluated) {
+ error(evaluated.error().message);
+ return ParseResult();
+ }
+
+ const type::Type type = (*parsed)->getType();
+ if (type.is<type::Array>()) {
+ // keep the original expression's array type, even if the evaluated
+ // type is more specific.
+ return ParseResult(std::make_unique<Literal>(
+ type.get<type::Array>(),
+ evaluated->get<std::vector<Value>>())
+ );
+ } else {
+ return ParseResult(std::make_unique<Literal>(*evaluated));
+ }
+ }
+
+ // if this is the root expression, enforce constraints on the use ["zoom"].
+ if (key.size() == 0 && parsed && !isZoomConstant(**parsed)) {
+ optional<variant<const InterpolateBase*, const Step*, ParsingError>> zoomCurve = findZoomCurve(parsed->get());
+ if (!zoomCurve) {
+ error(R"("zoom" expression may only be used as input to a top-level "step" or "interpolate" expression.)");
+ return ParseResult();
+ } else if (zoomCurve->is<ParsingError>()) {
+ error(zoomCurve->get<ParsingError>().message);
+ return ParseResult();
+ }
+ }
+
+ return parsed;
+}
+
+optional<std::string> ParsingContext::checkType(const type::Type& t) {
+ assert(expected);
+ optional<std::string> err = type::checkSubtype(*expected, t);
+ if (err) {
+ error(*err);
+ }
+ return err;
+}
+
+} // namespace expression
+} // namespace style
+} // namespace mbgl
diff --git a/src/mbgl/style/expression/step.cpp b/src/mbgl/style/expression/step.cpp
new file mode 100644
index 0000000000..2720e9257a
--- /dev/null
+++ b/src/mbgl/style/expression/step.cpp
@@ -0,0 +1,151 @@
+#include <mbgl/style/expression/step.hpp>
+#include <mbgl/style/expression/get_covering_stops.hpp>
+
+
+namespace mbgl {
+namespace style {
+namespace expression {
+
+EvaluationResult Step::evaluate(const EvaluationContext& params) const {
+ const EvaluationResult evaluatedInput = input->evaluate(params);
+ if (!evaluatedInput) { return evaluatedInput.error(); }
+ float x = *fromExpressionValue<float>(*evaluatedInput);
+
+ if (stops.empty()) {
+ return EvaluationError { "No stops in step curve." };
+ }
+
+ auto it = stops.upper_bound(x);
+ if (it == stops.end()) {
+ return stops.rbegin()->second->evaluate(params);
+ } else if (it == stops.begin()) {
+ return stops.begin()->second->evaluate(params);
+ } else {
+ return std::prev(it)->second->evaluate(params);
+ }
+}
+
+void Step::eachChild(const std::function<void(const Expression&)>& visit) const {
+ visit(*input);
+ for (auto it = stops.begin(); it != stops.end(); it++) {
+ visit(*it->second);
+ }
+}
+
+bool Step::operator==(const Expression& e) const {
+ if (auto rhs = dynamic_cast<const Step*>(&e)) {
+ return *input == *(rhs->input) && Expression::childrenEqual(stops, rhs->stops);
+ }
+ return false;
+}
+
+Range<float> Step::getCoveringStops(const double lower, const double upper) const {
+ return ::mbgl::style::expression::getCoveringStops(stops, lower, upper);
+}
+
+
+ParseResult Step::parse(const mbgl::style::conversion::Convertible& value, ParsingContext& ctx) {
+ assert(isArray(value));
+
+ auto length = arrayLength(value);
+
+ if (length - 1 < 4) {
+ ctx.error("Expected at least 4 arguments, but found only " + std::to_string(length - 1) + ".");
+ return ParseResult();
+ }
+
+ // [step, input, firstOutput_value, 2 * (n pairs)...]
+ if ((length - 1) % 2 != 0) {
+ ctx.error("Expected an even number of arguments.");
+ return ParseResult();
+ }
+
+ ParseResult input = ctx.parse(arrayMember(value, 1), 1, {type::Number});
+ if (!input) {
+ return input;
+ }
+
+ std::map<double, std::unique_ptr<Expression>> stops;
+ optional<type::Type> outputType;
+ if (ctx.getExpected() && *ctx.getExpected() != type::Value) {
+ outputType = ctx.getExpected();
+ }
+
+ double previous = - std::numeric_limits<double>::infinity();
+
+ // consume the first output value, which doesn't have a corresponding input value,
+ // before proceeding into the "stops" loop below.
+ auto firstOutput = ctx.parse(arrayMember(value, 2), 2, outputType);
+ if (!firstOutput) {
+ return ParseResult();
+ }
+ if (!outputType) {
+ outputType = (*firstOutput)->getType();
+ }
+ stops.emplace(-std::numeric_limits<double>::infinity(), std::move(*firstOutput));
+
+
+ for (std::size_t i = 3; i + 1 < length; i += 2) {
+ const optional<mbgl::Value> labelValue = toValue(arrayMember(value, i));
+ optional<double> label;
+ if (labelValue) {
+ labelValue->match(
+ [&](uint64_t n) {
+ if (n > std::numeric_limits<double>::max()) {
+ label = {std::numeric_limits<double>::infinity()};
+ } else {
+ label = {static_cast<double>(n)};
+ }
+ },
+ [&](int64_t n) {
+ if (n > std::numeric_limits<double>::max()) {
+ label = {std::numeric_limits<double>::infinity()};
+ } else {
+ label = {static_cast<double>(n)};
+ }
+ },
+ [&](double n) {
+ if (n > std::numeric_limits<double>::max()) {
+ label = {std::numeric_limits<double>::infinity()};
+ } else {
+ label = {static_cast<double>(n)};
+ }
+ },
+ [&](const auto&) {}
+ );
+ }
+ if (!label) {
+ ctx.error(R"(Input/output pairs for "step" expressions must be defined using literal numeric values (not computed expressions) for the input values.)", i);
+ return ParseResult();
+ }
+
+ if (*label <= previous) {
+ ctx.error(
+ R"(Input/output pairs for "step" expressions must be arranged with input values in strictly ascending order.)",
+ i
+ );
+ return ParseResult();
+ }
+ previous = *label;
+
+ auto output = ctx.parse(arrayMember(value, i + 1), i + 1, outputType);
+ if (!output) {
+ return ParseResult();
+ }
+ if (!outputType) {
+ outputType = (*output)->getType();
+ }
+
+ stops.emplace(*label, std::move(*output));
+ }
+
+ assert(outputType);
+
+ return ParseResult(std::make_unique<Step>(*outputType, std::move(*input), std::move(stops)));
+}
+
+
+} // namespace expression
+} // namespace style
+} // namespace mbgl
+
diff --git a/src/mbgl/style/expression/util.cpp b/src/mbgl/style/expression/util.cpp
new file mode 100644
index 0000000000..f198fb3e1b
--- /dev/null
+++ b/src/mbgl/style/expression/util.cpp
@@ -0,0 +1,39 @@
+
+#include <mbgl/style/expression/util.hpp>
+#include <mbgl/style/expression/value.hpp>
+
+namespace mbgl {
+namespace style {
+namespace expression {
+
+std::string stringifyColor(double r, double g, double b, double a) {
+ return stringify(r) + ", " +
+ stringify(g) + ", " +
+ stringify(b) + ", " +
+ stringify(a);
+}
+
+Result<Color> rgba(double r, double g, double b, double a) {
+ if (
+ r < 0 || r > 255 ||
+ g < 0 || g > 255 ||
+ b < 0 || b > 255
+ ) {
+ return EvaluationError {
+ "Invalid rgba value [" + stringifyColor(r, g, b, a) +
+ "]: 'r', 'g', and 'b' must be between 0 and 255."
+ };
+ }
+ if (a < 0 || a > 1) {
+ return EvaluationError {
+ "Invalid rgba value [" + stringifyColor(r, g, b, a) +
+ "]: 'a' must be between 0 and 1."
+ };
+ }
+ return Color(r / 255, g / 255, b / 255, a);
+}
+
+} // namespace expression
+} // namespace style
+} // namespace mbgl
+
diff --git a/src/mbgl/style/expression/util.hpp b/src/mbgl/style/expression/util.hpp
new file mode 100644
index 0000000000..b6fc408ed9
--- /dev/null
+++ b/src/mbgl/style/expression/util.hpp
@@ -0,0 +1,14 @@
+#pragma once
+
+#include <mbgl/style/expression/expression.hpp>
+#include <mbgl/util/color.hpp>
+
+namespace mbgl {
+namespace style {
+namespace expression {
+
+Result<Color> rgba(double r, double g, double b, double a);
+
+} // namespace expression
+} // namespace style
+} // namespace mbgl
diff --git a/src/mbgl/style/expression/value.cpp b/src/mbgl/style/expression/value.cpp
new file mode 100644
index 0000000000..b75f471ce3
--- /dev/null
+++ b/src/mbgl/style/expression/value.cpp
@@ -0,0 +1,322 @@
+#include <rapidjson/writer.h>
+#include <rapidjson/stringbuffer.h>
+#include <mbgl/style/expression/value.hpp>
+
+namespace mbgl {
+namespace style {
+namespace expression {
+
+type::Type typeOf(const Value& value) {
+ return value.match(
+ [&](bool) -> type::Type { return type::Boolean; },
+ [&](double) -> type::Type { return type::Number; },
+ [&](const std::string&) -> type::Type { return type::String; },
+ [&](const Color&) -> type::Type { return type::Color; },
+ [&](const NullValue&) -> type::Type { return type::Null; },
+ [&](const std::unordered_map<std::string, Value>&) -> type::Type { return type::Object; },
+ [&](const std::vector<Value>& arr) -> type::Type {
+ optional<type::Type> itemType;
+ for (const auto& item : arr) {
+ const type::Type t = typeOf(item);
+ if (!itemType) {
+ itemType = {t};
+ } else if (*itemType == t) {
+ continue;
+ } else {
+ itemType = {type::Value};
+ break;
+ }
+ }
+
+ return type::Array(itemType.value_or(type::Value), arr.size());
+ }
+ );
+}
+
+void writeJSON(rapidjson::Writer<rapidjson::StringBuffer>& writer, const Value& value) {
+ value.match(
+ [&] (const NullValue&) { writer.Null(); },
+ [&] (bool b) { writer.Bool(b); },
+ [&] (double f) {
+ // make sure integer values are stringified without trailing ".0".
+ f == std::floor(f) ? writer.Int(f) : writer.Double(f);
+ },
+ [&] (const std::string& s) { writer.String(s); },
+ [&] (const Color& c) { writer.String(c.stringify()); },
+ [&] (const std::vector<Value>& arr) {
+ writer.StartArray();
+ for(const auto& item : arr) {
+ writeJSON(writer, item);
+ }
+ writer.EndArray();
+ },
+ [&] (const std::unordered_map<std::string, Value>& obj) {
+ writer.StartObject();
+ for(const auto& entry : obj) {
+ writer.Key(entry.first.c_str());
+ writeJSON(writer, entry.second);
+ }
+ writer.EndObject();
+ }
+ );
+}
+
+std::string stringify(const Value& value) {
+ rapidjson::StringBuffer buffer;
+ rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
+ writeJSON(writer, value);
+ return buffer.GetString();
+}
+
+struct FromMBGLValue {
+ Value operator()(const std::vector<mbgl::Value>& v) {
+ std::vector<Value> result;
+ result.reserve(v.size());
+ for(const auto& item : v) {
+ result.emplace_back(toExpressionValue(item));
+ }
+ return result;
+ }
+
+ Value operator()(const std::unordered_map<std::string, mbgl::Value>& v) {
+ std::unordered_map<std::string, Value> result;
+ result.reserve(v.size());
+ for(const auto& entry : v) {
+ result.emplace(entry.first, toExpressionValue(entry.second));
+ }
+ return result;
+ }
+
+ Value operator()(const std::string& s) { return s; }
+ Value operator()(const bool b) { return b; }
+ Value operator()(const NullValue) { return Null; }
+ Value operator()(const double v) { return v; }
+ Value operator()(const uint64_t& v) {
+ return static_cast<double>(v);
+ }
+ Value operator()(const int64_t& v) {
+ return static_cast<double>(v);
+ }
+};
+
+Value ValueConverter<mbgl::Value>::toExpressionValue(const mbgl::Value& value) {
+ return mbgl::Value::visit(value, FromMBGLValue());
+}
+
+Value ValueConverter<float>::toExpressionValue(const float value) {
+ return static_cast<double>(value);
+}
+
+optional<float> ValueConverter<float>::fromExpressionValue(const Value& value) {
+ if (value.template is<double>()) {
+ double v = value.template get<double>();
+ if (v <= std::numeric_limits<float>::max()) {
+ return static_cast<float>(v);
+ }
+ }
+ return optional<float>();
+}
+
+
+template <typename T, typename Container>
+std::vector<Value> toArrayValue(const Container& value) {
+ std::vector<Value> result;
+ result.reserve(value.size());
+ for (const T& item : value) {
+ result.push_back(ValueConverter<T>::toExpressionValue(item));
+ }
+ return result;
+}
+
+template <typename T, std::size_t N>
+Value ValueConverter<std::array<T, N>>::toExpressionValue(const std::array<T, N>& value) {
+ return toArrayValue<T>(value);
+}
+
+template <typename T, std::size_t N>
+optional<std::array<T, N>> ValueConverter<std::array<T, N>>::fromExpressionValue(const Value& value) {
+ return value.match(
+ [&] (const std::vector<Value>& v) -> optional<std::array<T, N>> {
+ if (v.size() != N) return optional<std::array<T, N>>();
+ std::array<T, N> result;
+ auto it = result.begin();
+ for(const Value& item : v) {
+ optional<T> convertedItem = ValueConverter<T>::fromExpressionValue(item);
+ if (!convertedItem) {
+ return optional<std::array<T, N>>();
+ }
+ *it = *convertedItem;
+ it = std::next(it);
+ }
+ return result;
+ },
+ [&] (const auto&) { return optional<std::array<T, N>>(); }
+ );
+}
+
+
+template <typename T>
+Value ValueConverter<std::vector<T>>::toExpressionValue(const std::vector<T>& value) {
+ return toArrayValue<T>(value);
+}
+
+template <typename T>
+optional<std::vector<T>> ValueConverter<std::vector<T>>::fromExpressionValue(const Value& value) {
+ return value.match(
+ [&] (const std::vector<Value>& v) -> optional<std::vector<T>> {
+ std::vector<T> result;
+ result.reserve(v.size());
+ for(const Value& item : v) {
+ optional<T> convertedItem = ValueConverter<T>::fromExpressionValue(item);
+ if (!convertedItem) {
+ return optional<std::vector<T>>();
+ }
+ result.push_back(*convertedItem);
+ }
+ return result;
+ },
+ [&] (const auto&) { return optional<std::vector<T>>(); }
+ );
+}
+
+Value ValueConverter<Position>::toExpressionValue(const mbgl::style::Position& value) {
+ return ValueConverter<std::array<float, 3>>::toExpressionValue(value.getSpherical());
+}
+
+optional<Position> ValueConverter<Position>::fromExpressionValue(const Value& v) {
+ auto pos = ValueConverter<std::array<float, 3>>::fromExpressionValue(v);
+ return pos ? optional<Position>(Position(*pos)) : optional<Position>();
+}
+
+template <typename T>
+Value ValueConverter<T, std::enable_if_t< std::is_enum<T>::value >>::toExpressionValue(const T& value) {
+ return std::string(Enum<T>::toString(value));
+}
+
+template <typename T>
+optional<T> ValueConverter<T, std::enable_if_t< std::is_enum<T>::value >>::fromExpressionValue(const Value& value) {
+ return value.match(
+ [&] (const std::string& v) { return Enum<T>::toEnum(v); },
+ [&] (const auto&) { return optional<T>(); }
+ );
+}
+
+
+Value toExpressionValue(const Value& v) {
+ return v;
+}
+
+template <typename T, typename Enable>
+Value toExpressionValue(const T& value) {
+ return ValueConverter<T>::toExpressionValue(value);
+}
+
+optional<Value> fromExpressionValue(const Value& v) {
+ return optional<Value>(v);
+}
+
+template <typename T>
+std::enable_if_t< !std::is_convertible<T, Value>::value,
+optional<T>> fromExpressionValue(const Value& v)
+{
+ return ValueConverter<T>::fromExpressionValue(v);
+}
+
+template <typename T>
+type::Type valueTypeToExpressionType() {
+ return ValueConverter<T>::expressionType();
+}
+
+template <> type::Type valueTypeToExpressionType<Value>() { return type::Value; }
+template <> type::Type valueTypeToExpressionType<NullValue>() { return type::Null; }
+template <> type::Type valueTypeToExpressionType<bool>() { return type::Boolean; }
+template <> type::Type valueTypeToExpressionType<double>() { return type::Number; }
+template <> type::Type valueTypeToExpressionType<std::string>() { return type::String; }
+template <> type::Type valueTypeToExpressionType<Color>() { return type::Color; }
+template <> type::Type valueTypeToExpressionType<std::unordered_map<std::string, Value>>() { return type::Object; }
+template <> type::Type valueTypeToExpressionType<std::vector<Value>>() { return type::Array(type::Value); }
+
+// used only for the special (and private) "error" expression
+template <> type::Type valueTypeToExpressionType<type::ErrorType>() { return type::Error; }
+
+
+template Value toExpressionValue(const mbgl::Value&);
+
+
+// for to_rgba expression
+template type::Type valueTypeToExpressionType<std::array<double, 4>>();
+template optional<std::array<double, 4>> fromExpressionValue<std::array<double, 4>>(const Value&);
+template Value toExpressionValue(const std::array<double, 4>&);
+
+// layout/paint property types
+template type::Type valueTypeToExpressionType<float>();
+template optional<float> fromExpressionValue<float>(const Value&);
+template Value toExpressionValue(const float&);
+
+template type::Type valueTypeToExpressionType<std::array<float, 2>>();
+template optional<std::array<float, 2>> fromExpressionValue<std::array<float, 2>>(const Value&);
+template Value toExpressionValue(const std::array<float, 2>&);
+
+template type::Type valueTypeToExpressionType<std::array<float, 4>>();
+template optional<std::array<float, 4>> fromExpressionValue<std::array<float, 4>>(const Value&);
+template Value toExpressionValue(const std::array<float, 4>&);
+
+template type::Type valueTypeToExpressionType<std::vector<float>>();
+template optional<std::vector<float>> fromExpressionValue<std::vector<float>>(const Value&);
+template Value toExpressionValue(const std::vector<float>&);
+
+template type::Type valueTypeToExpressionType<std::vector<std::string>>();
+template optional<std::vector<std::string>> fromExpressionValue<std::vector<std::string>>(const Value&);
+template Value toExpressionValue(const std::vector<std::string>&);
+
+template type::Type valueTypeToExpressionType<AlignmentType>();
+template optional<AlignmentType> fromExpressionValue<AlignmentType>(const Value&);
+template Value toExpressionValue(const AlignmentType&);
+
+template type::Type valueTypeToExpressionType<CirclePitchScaleType>();
+template optional<CirclePitchScaleType> fromExpressionValue<CirclePitchScaleType>(const Value&);
+template Value toExpressionValue(const CirclePitchScaleType&);
+
+template type::Type valueTypeToExpressionType<IconTextFitType>();
+template optional<IconTextFitType> fromExpressionValue<IconTextFitType>(const Value&);
+template Value toExpressionValue(const IconTextFitType&);
+
+template type::Type valueTypeToExpressionType<LineCapType>();
+template optional<LineCapType> fromExpressionValue<LineCapType>(const Value&);
+template Value toExpressionValue(const LineCapType&);
+
+template type::Type valueTypeToExpressionType<LineJoinType>();
+template optional<LineJoinType> fromExpressionValue<LineJoinType>(const Value&);
+template Value toExpressionValue(const LineJoinType&);
+
+template type::Type valueTypeToExpressionType<SymbolPlacementType>();
+template optional<SymbolPlacementType> fromExpressionValue<SymbolPlacementType>(const Value&);
+template Value toExpressionValue(const SymbolPlacementType&);
+
+template type::Type valueTypeToExpressionType<SymbolAnchorType>();
+template optional<SymbolAnchorType> fromExpressionValue<SymbolAnchorType>(const Value&);
+template Value toExpressionValue(const SymbolAnchorType&);
+
+template type::Type valueTypeToExpressionType<TextJustifyType>();
+template optional<TextJustifyType> fromExpressionValue<TextJustifyType>(const Value&);
+template Value toExpressionValue(const TextJustifyType&);
+
+template type::Type valueTypeToExpressionType<TextTransformType>();
+template optional<TextTransformType> fromExpressionValue<TextTransformType>(const Value&);
+template Value toExpressionValue(const TextTransformType&);
+
+template type::Type valueTypeToExpressionType<TranslateAnchorType>();
+template optional<TranslateAnchorType> fromExpressionValue<TranslateAnchorType>(const Value&);
+template Value toExpressionValue(const TranslateAnchorType&);
+
+template type::Type valueTypeToExpressionType<LightAnchorType>();
+template optional<LightAnchorType> fromExpressionValue<LightAnchorType>(const Value&);
+template Value toExpressionValue(const LightAnchorType&);
+
+template type::Type valueTypeToExpressionType<Position>();
+template optional<Position> fromExpressionValue<Position>(const Value&);
+template Value toExpressionValue(const Position&);
+
+} // namespace expression
+} // namespace style
+} // namespace mbgl
diff --git a/src/mbgl/style/function/expression.cpp b/src/mbgl/style/function/expression.cpp
new file mode 100644
index 0000000000..d9dbbfa1d3
--- /dev/null
+++ b/src/mbgl/style/function/expression.cpp
@@ -0,0 +1,38 @@
+#include <mbgl/style/expression/expression.hpp>
+#include <mbgl/style/expression/compound_expression.hpp>
+#include <mbgl/tile/geometry_tile_data.hpp>
+
+namespace mbgl {
+namespace style {
+namespace expression {
+
+class GeoJSONFeature : public GeometryTileFeature {
+public:
+ const Feature& feature;
+
+ GeoJSONFeature(const Feature& feature_) : feature(feature_) {}
+
+ FeatureType getType() const override {
+ return apply_visitor(ToFeatureType(), feature.geometry);
+ }
+ PropertyMap getProperties() const override { return feature.properties; }
+ optional<FeatureIdentifier> getID() const override { return feature.id; }
+ GeometryCollection getGeometries() const override { return {}; }
+ optional<mbgl::Value> getValue(const std::string& key) const override {
+ auto it = feature.properties.find(key);
+ if (it != feature.properties.end()) {
+ return optional<mbgl::Value>(it->second);
+ }
+ return optional<mbgl::Value>();
+ }
+};
+
+
+EvaluationResult Expression::evaluate(optional<float> zoom, const Feature& feature, optional<double> heatmapDensity) const {
+ GeoJSONFeature f(feature);
+ return this->evaluate(EvaluationContext(zoom, &f, heatmapDensity));
+}
+
+} // namespace expression
+} // namespace style
+} // namespace mbgl
diff --git a/src/mbgl/style/parser.cpp b/src/mbgl/style/parser.cpp
index a83897dbf5..10fce33986 100644
--- a/src/mbgl/style/parser.cpp
+++ b/src/mbgl/style/parser.cpp
@@ -1,11 +1,13 @@
#include <mbgl/style/parser.hpp>
#include <mbgl/style/layer_impl.hpp>
+#include <mbgl/style/layers/symbol_layer.hpp>
#include <mbgl/style/rapidjson_conversion.hpp>
#include <mbgl/style/conversion.hpp>
#include <mbgl/style/conversion/coordinate.hpp>
#include <mbgl/style/conversion/source.hpp>
#include <mbgl/style/conversion/layer.hpp>
#include <mbgl/style/conversion/light.hpp>
+#include <mbgl/style/conversion/transition_options.hpp>
#include <mbgl/util/logging.hpp>
#include <mbgl/util/string.hpp>
@@ -149,7 +151,7 @@ void Parser::parseSources(const JSValue& value) {
}
for (const auto& property : value.GetObject()) {
- std::string id = *conversion::toString(property.name);
+ std::string id { property.name.GetString(), property.name.GetStringLength() };
conversion::Error error;
optional<std::unique_ptr<Source>> source =
@@ -256,7 +258,7 @@ void Parser::parseLayer(const std::string& id, const JSValue& value, std::unique
}
layer = reference->cloneRef(id);
- conversion::setPaintProperties(*layer, value);
+ conversion::setPaintProperties(*layer, conversion::Convertible(&value));
} else {
conversion::Error error;
optional<std::unique_ptr<Layer>> converted = conversion::convert<std::unique_ptr<Layer>>(value, error);
diff --git a/src/mbgl/style/rapidjson_conversion.hpp b/src/mbgl/style/rapidjson_conversion.hpp
index 48a764ccb4..79bd9c928b 100644
--- a/src/mbgl/style/rapidjson_conversion.hpp
+++ b/src/mbgl/style/rapidjson_conversion.hpp
@@ -1,103 +1,125 @@
#pragma once
#include <mbgl/util/rapidjson.hpp>
-#include <mbgl/util/feature.hpp>
#include <mbgl/style/conversion.hpp>
+#include <mapbox/geojson.hpp>
+#include <mapbox/geojson/rapidjson.hpp>
+
namespace mbgl {
namespace style {
namespace conversion {
-inline bool isUndefined(const JSValue& value) {
- return value.IsNull();
-}
-
-inline bool isArray(const JSValue& value) {
- return value.IsArray();
-}
+template <>
+class ConversionTraits<const JSValue*> {
+public:
+ static bool isUndefined(const JSValue* value) {
+ return value->IsNull();
+ }
-inline std::size_t arrayLength(const JSValue& value) {
- return value.Size();
-}
+ static bool isArray(const JSValue* value) {
+ return value->IsArray();
+ }
-inline const JSValue& arrayMember(const JSValue& value, std::size_t i) {
- return value[rapidjson::SizeType(i)];
-}
+ static std::size_t arrayLength(const JSValue* value) {
+ return value->Size();
+ }
-inline bool isObject(const JSValue& value) {
- return value.IsObject();
-}
+ static const JSValue* arrayMember(const JSValue* value, std::size_t i) {
+ return &(*value)[rapidjson::SizeType(i)];
+ }
-inline const JSValue* objectMember(const JSValue& value, const char * name) {
- if (!value.HasMember(name)) {
- return nullptr;
+ static bool isObject(const JSValue* value) {
+ return value->IsObject();
}
- return &value[name];
-}
-template <class Fn>
-optional<Error> eachMember(const JSValue& value, Fn&& fn) {
- assert(value.IsObject());
- for (const auto& property : value.GetObject()) {
- optional<Error> result =
- fn({ property.name.GetString(), property.name.GetStringLength() }, property.value);
- if (result) {
- return result;
+ static optional<const JSValue*> objectMember(const JSValue* value, const char * name) {
+ if (!value->HasMember(name)) {
+ return optional<const JSValue*>();
}
+ const JSValue* const& member = &(*value)[name];
+ return {member};
}
- return {};
-}
-inline optional<bool> toBool(const JSValue& value) {
- if (!value.IsBool()) {
+ template <class Fn>
+ static optional<Error> eachMember(const JSValue* value, Fn&& fn) {
+ assert(value->IsObject());
+ for (const auto& property : value->GetObject()) {
+ optional<Error> result =
+ fn({ property.name.GetString(), property.name.GetStringLength() }, &property.value);
+ if (result) {
+ return result;
+ }
+ }
return {};
}
- return value.GetBool();
-}
-inline optional<float> toNumber(const JSValue& value) {
- if (!value.IsNumber()) {
- return {};
+ static optional<bool> toBool(const JSValue* value) {
+ if (!value->IsBool()) {
+ return {};
+ }
+ return value->GetBool();
}
- return value.GetDouble();
-}
-inline optional<double> toDouble(const JSValue& value) {
- if (!value.IsNumber()) {
- return {};
+ static optional<float> toNumber(const JSValue* value) {
+ if (!value->IsNumber()) {
+ return {};
+ }
+ return value->GetDouble();
}
- return value.GetDouble();
-}
-inline optional<std::string> toString(const JSValue& value) {
- if (!value.IsString()) {
- return {};
+ static optional<double> toDouble(const JSValue* value) {
+ if (!value->IsNumber()) {
+ return {};
+ }
+ return value->GetDouble();
+ }
+
+ static optional<std::string> toString(const JSValue* value) {
+ if (!value->IsString()) {
+ return {};
+ }
+ return {{ value->GetString(), value->GetStringLength() }};
}
- return {{ value.GetString(), value.GetStringLength() }};
-}
-inline optional<Value> toValue(const JSValue& value) {
- switch (value.GetType()) {
- case rapidjson::kNullType:
- case rapidjson::kFalseType:
- return { false };
+ static optional<Value> toValue(const JSValue* value) {
+ switch (value->GetType()) {
+ case rapidjson::kNullType:
+ case rapidjson::kFalseType:
+ return { false };
- case rapidjson::kTrueType:
- return { true };
+ case rapidjson::kTrueType:
+ return { true };
- case rapidjson::kStringType:
- return { std::string { value.GetString(), value.GetStringLength() } };
+ case rapidjson::kStringType:
+ return { std::string { value->GetString(), value->GetStringLength() } };
- case rapidjson::kNumberType:
- if (value.IsUint64()) return { value.GetUint64() };
- if (value.IsInt64()) return { value.GetInt64() };
- return { value.GetDouble() };
+ case rapidjson::kNumberType:
+ if (value->IsUint64()) return { value->GetUint64() };
+ if (value->IsInt64()) return { value->GetInt64() };
+ return { value->GetDouble() };
- default:
+ default:
+ return {};
+ }
+ }
+
+ static optional<GeoJSON> toGeoJSON(const JSValue* value, Error& error) {
+ try {
+ return mapbox::geojson::convert(*value);
+ } catch (const std::exception& ex) {
+ error = { ex.what() };
return {};
+ }
}
+};
+
+template <class T, class...Args>
+optional<T> convert(const JSValue& value, Error& error, Args&&...args) {
+ return convert<T>(Convertible(&value), error, std::forward<Args>(args)...);
}
} // namespace conversion
} // namespace style
} // namespace mbgl
+
diff --git a/src/mbgl/text/glyph_manager.cpp b/src/mbgl/text/glyph_manager.cpp
index 916d39ae62..c79a1938c1 100644
--- a/src/mbgl/text/glyph_manager.cpp
+++ b/src/mbgl/text/glyph_manager.cpp
@@ -36,8 +36,9 @@ void GlyphManager::getGlyphs(GlyphRequestor& requestor, GlyphDependencies glyphD
for (const auto& range : ranges) {
auto it = entry.ranges.find(range);
if (it == entry.ranges.end() || !it->second.parsed) {
- GlyphRequest& request = requestRange(entry, fontStack, range);
+ GlyphRequest& request = entry.ranges[range];
request.requestors[&requestor] = dependencies;
+ requestRange(request, fontStack, range);
}
}
}
@@ -49,18 +50,14 @@ void GlyphManager::getGlyphs(GlyphRequestor& requestor, GlyphDependencies glyphD
}
}
-GlyphManager::GlyphRequest& GlyphManager::requestRange(Entry& entry, const FontStack& fontStack, const GlyphRange& range) {
- GlyphRequest& request = entry.ranges[range];
-
+void GlyphManager::requestRange(GlyphRequest& request, const FontStack& fontStack, const GlyphRange& range) {
if (request.req) {
- return request;
+ return;
}
request.req = fileSource.request(Resource::glyphs(glyphURL, fontStack, range), [this, fontStack, range](Response res) {
processResponse(res, fontStack, range);
});
-
- return request;
}
void GlyphManager::processResponse(const Response& res, const FontStack& fontStack, const GlyphRange& range) {
diff --git a/src/mbgl/text/glyph_manager.hpp b/src/mbgl/text/glyph_manager.hpp
index 00df079462..de2b9cde7b 100644
--- a/src/mbgl/text/glyph_manager.hpp
+++ b/src/mbgl/text/glyph_manager.hpp
@@ -58,7 +58,7 @@ private:
std::unordered_map<FontStack, Entry, FontStackHash> entries;
- GlyphRequest& requestRange(Entry&, const FontStack&, const GlyphRange&);
+ void requestRange(GlyphRequest&, const FontStack&, const GlyphRange&);
void processResponse(const Response&, const FontStack&, const GlyphRange&);
void notify(GlyphRequestor&, const GlyphDependencies&);
diff --git a/src/mbgl/util/mapbox.cpp b/src/mbgl/util/mapbox.cpp
index 8cbc85d492..802b527a26 100644
--- a/src/mbgl/util/mapbox.cpp
+++ b/src/mbgl/util/mapbox.cpp
@@ -114,7 +114,7 @@ std::string normalizeTileURL(const std::string& baseURL,
}
std::string
-canonicalizeTileURL(const std::string& str, const SourceType type, const uint16_t tileSize) {
+canonicalizeTileURL(const std::string& str, const style::SourceType type, const uint16_t tileSize) {
const char* version = "/v4/";
const size_t versionLen = strlen(version);
@@ -133,7 +133,7 @@ canonicalizeTileURL(const std::string& str, const SourceType type, const uint16_
std::string result = "mapbox://tiles/";
result.append(str, path.directory.first + versionLen, path.directory.second - versionLen);
result.append(str, path.filename.first, path.filename.second);
- if (type == SourceType::Raster) {
+ if (type == style::SourceType::Raster) {
result += tileSize == util::tileSize ? "@2x" : "{ratio}";
}
@@ -171,7 +171,7 @@ canonicalizeTileURL(const std::string& str, const SourceType type, const uint16_
return result;
}
-void canonicalizeTileset(Tileset& tileset, const std::string& sourceURL, SourceType type, uint16_t tileSize) {
+void canonicalizeTileset(Tileset& tileset, const std::string& sourceURL, style::SourceType type, uint16_t tileSize) {
// TODO: Remove this hack by delivering proper URLs in the TileJSON to begin with.
if (isMapboxURL(sourceURL)) {
for (auto& url : tileset.tiles) {
diff --git a/src/mbgl/util/mapbox.hpp b/src/mbgl/util/mapbox.hpp
index f3dfdd0b01..aa128f2667 100644
--- a/src/mbgl/util/mapbox.hpp
+++ b/src/mbgl/util/mapbox.hpp
@@ -19,10 +19,10 @@ std::string normalizeGlyphsURL(const std::string& baseURL, const std::string& ur
std::string normalizeTileURL(const std::string& baseURL, const std::string& url, const std::string& accessToken);
// Return a "mapbox://tiles/..." URL (suitable for normalizeTileURL) for the given Mapbox tile URL.
-std::string canonicalizeTileURL(const std::string& url, SourceType, uint16_t tileSize);
+std::string canonicalizeTileURL(const std::string& url, style::SourceType, uint16_t tileSize);
// Replace URL templates with "mapbox://tiles/..." URLs (suitable for normalizeTileURL).
-void canonicalizeTileset(Tileset&, const std::string& url, SourceType, uint16_t tileSize);
+void canonicalizeTileset(Tileset&, const std::string& url, style::SourceType, uint16_t tileSize);
extern const uint64_t DEFAULT_OFFLINE_TILE_COUNT_LIMIT;
diff --git a/src/mbgl/util/tile_cover.cpp b/src/mbgl/util/tile_cover.cpp
index a5a1b1d70c..39b562d811 100644
--- a/src/mbgl/util/tile_cover.cpp
+++ b/src/mbgl/util/tile_cover.cpp
@@ -126,9 +126,9 @@ std::vector<UnwrappedTileID> tileCover(const Point<double>& tl,
} // namespace
-int32_t coveringZoomLevel(double zoom, SourceType type, uint16_t size) {
+int32_t coveringZoomLevel(double zoom, style::SourceType type, uint16_t size) {
zoom += std::log(util::tileSize / size) / std::log(2);
- if (type == SourceType::Raster || type == SourceType::Video) {
+ if (type == style::SourceType::Raster || type == style::SourceType::Video) {
return ::round(zoom);
} else {
return std::floor(zoom);
@@ -182,10 +182,10 @@ uint64_t tileCount(const LatLngBounds& bounds, uint8_t zoom, uint16_t tileSize_)
auto y1 = floor(sw.y/ tileSize_);
auto y2 = floor((ne.y - 1) / tileSize_);
- auto minX = std::fmax(std::min(x1, x2), 0);
+ auto minX = ::fmax(std::min(x1, x2), 0);
auto maxX = std::max(x1, x2);
auto minY = (std::pow(2, zoom) - 1) - std::max(y1, y2);
- auto maxY = (std::pow(2, zoom) - 1) - std::fmax(std::min(y1, y2), 0);
+ auto maxY = (std::pow(2, zoom) - 1) - ::fmax(std::min(y1, y2), 0);
return (maxX - minX + 1) * (maxY - minY + 1);
}
diff --git a/src/mbgl/util/tile_cover.hpp b/src/mbgl/util/tile_cover.hpp
index 3c7a4ee44a..b2098b59b8 100644
--- a/src/mbgl/util/tile_cover.hpp
+++ b/src/mbgl/util/tile_cover.hpp
@@ -13,7 +13,7 @@ class LatLngBounds;
namespace util {
-int32_t coveringZoomLevel(double z, SourceType type, uint16_t tileSize);
+int32_t coveringZoomLevel(double z, style::SourceType type, uint16_t tileSize);
std::vector<UnwrappedTileID> tileCover(const TransformState&, int32_t z);
std::vector<UnwrappedTileID> tileCover(const LatLngBounds&, int32_t z);
diff --git a/test/fixtures/expression_equality/acos.a.json b/test/fixtures/expression_equality/acos.a.json
new file mode 100644
index 0000000000..1e9bb752ca
--- /dev/null
+++ b/test/fixtures/expression_equality/acos.a.json
@@ -0,0 +1,4 @@
+[
+ "acos",
+ 0.5
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/acos.b.json b/test/fixtures/expression_equality/acos.b.json
new file mode 100644
index 0000000000..54e035cb7e
--- /dev/null
+++ b/test/fixtures/expression_equality/acos.b.json
@@ -0,0 +1,4 @@
+[
+ "acos",
+ 1.5
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/all.a.json b/test/fixtures/expression_equality/all.a.json
new file mode 100644
index 0000000000..ec7154b7b9
--- /dev/null
+++ b/test/fixtures/expression_equality/all.a.json
@@ -0,0 +1,17 @@
+[
+ "all",
+ [
+ "boolean",
+ [
+ "get",
+ "x"
+ ]
+ ],
+ [
+ "boolean",
+ [
+ "get",
+ "y"
+ ]
+ ]
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/all.b.json b/test/fixtures/expression_equality/all.b.json
new file mode 100644
index 0000000000..8eab839bb0
--- /dev/null
+++ b/test/fixtures/expression_equality/all.b.json
@@ -0,0 +1,17 @@
+[
+ "all",
+ [
+ "boolean",
+ [
+ "get",
+ "x"
+ ]
+ ],
+ [
+ "boolean",
+ [
+ "get",
+ "y_other"
+ ]
+ ]
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/any.a.json b/test/fixtures/expression_equality/any.a.json
new file mode 100644
index 0000000000..3f044c1f79
--- /dev/null
+++ b/test/fixtures/expression_equality/any.a.json
@@ -0,0 +1,17 @@
+[
+ "any",
+ [
+ "boolean",
+ [
+ "get",
+ "x"
+ ]
+ ],
+ [
+ "boolean",
+ [
+ "get",
+ "y"
+ ]
+ ]
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/any.b.json b/test/fixtures/expression_equality/any.b.json
new file mode 100644
index 0000000000..720662751f
--- /dev/null
+++ b/test/fixtures/expression_equality/any.b.json
@@ -0,0 +1,17 @@
+[
+ "any",
+ [
+ "boolean",
+ [
+ "get",
+ "x"
+ ]
+ ],
+ [
+ "boolean",
+ [
+ "get",
+ "y_other"
+ ]
+ ]
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/array.a.json b/test/fixtures/expression_equality/array.a.json
new file mode 100644
index 0000000000..3c31303ca3
--- /dev/null
+++ b/test/fixtures/expression_equality/array.a.json
@@ -0,0 +1,11 @@
+[
+ "array",
+ [
+ "literal",
+ [
+ 1,
+ 2,
+ 3
+ ]
+ ]
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/array.b.json b/test/fixtures/expression_equality/array.b.json
new file mode 100644
index 0000000000..7606794d56
--- /dev/null
+++ b/test/fixtures/expression_equality/array.b.json
@@ -0,0 +1,11 @@
+[
+ "array",
+ [
+ "literal",
+ [
+ 1,
+ 2,
+ 4
+ ]
+ ]
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/asin.a.json b/test/fixtures/expression_equality/asin.a.json
new file mode 100644
index 0000000000..3cd730ccbf
--- /dev/null
+++ b/test/fixtures/expression_equality/asin.a.json
@@ -0,0 +1,4 @@
+[
+ "asin",
+ 0.5
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/asin.b.json b/test/fixtures/expression_equality/asin.b.json
new file mode 100644
index 0000000000..2c862c8cbe
--- /dev/null
+++ b/test/fixtures/expression_equality/asin.b.json
@@ -0,0 +1,4 @@
+[
+ "asin",
+ 1.5
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/at.a.json b/test/fixtures/expression_equality/at.a.json
new file mode 100644
index 0000000000..c69b0d933b
--- /dev/null
+++ b/test/fixtures/expression_equality/at.a.json
@@ -0,0 +1,20 @@
+[
+ "number",
+ [
+ "at",
+ [
+ "number",
+ [
+ "get",
+ "i"
+ ]
+ ],
+ [
+ "array",
+ [
+ "get",
+ "arr"
+ ]
+ ]
+ ]
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/at.b.json b/test/fixtures/expression_equality/at.b.json
new file mode 100644
index 0000000000..6e19c28606
--- /dev/null
+++ b/test/fixtures/expression_equality/at.b.json
@@ -0,0 +1,20 @@
+[
+ "number",
+ [
+ "at",
+ [
+ "number",
+ [
+ "get",
+ "i"
+ ]
+ ],
+ [
+ "array",
+ [
+ "get",
+ "arr_other"
+ ]
+ ]
+ ]
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/atan.a.json b/test/fixtures/expression_equality/atan.a.json
new file mode 100644
index 0000000000..b76406bc44
--- /dev/null
+++ b/test/fixtures/expression_equality/atan.a.json
@@ -0,0 +1,4 @@
+[
+ "atan",
+ 1
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/atan.b.json b/test/fixtures/expression_equality/atan.b.json
new file mode 100644
index 0000000000..aafbbb0594
--- /dev/null
+++ b/test/fixtures/expression_equality/atan.b.json
@@ -0,0 +1,4 @@
+[
+ "atan",
+ 2
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/boolean.a.json b/test/fixtures/expression_equality/boolean.a.json
new file mode 100644
index 0000000000..1230a2a926
--- /dev/null
+++ b/test/fixtures/expression_equality/boolean.a.json
@@ -0,0 +1,7 @@
+[
+ "boolean",
+ [
+ "get",
+ "x"
+ ]
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/boolean.b.json b/test/fixtures/expression_equality/boolean.b.json
new file mode 100644
index 0000000000..1ae91ef60c
--- /dev/null
+++ b/test/fixtures/expression_equality/boolean.b.json
@@ -0,0 +1,7 @@
+[
+ "boolean",
+ [
+ "get",
+ "x_other"
+ ]
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/case.a.json b/test/fixtures/expression_equality/case.a.json
new file mode 100644
index 0000000000..84049294f5
--- /dev/null
+++ b/test/fixtures/expression_equality/case.a.json
@@ -0,0 +1,14 @@
+[
+ "case",
+ [
+ "get",
+ "x"
+ ],
+ "x",
+ [
+ "get",
+ "y"
+ ],
+ "y",
+ "otherwise"
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/case.b.json b/test/fixtures/expression_equality/case.b.json
new file mode 100644
index 0000000000..038806043f
--- /dev/null
+++ b/test/fixtures/expression_equality/case.b.json
@@ -0,0 +1,14 @@
+[
+ "case",
+ [
+ "get",
+ "x"
+ ],
+ "x",
+ [
+ "get",
+ "y"
+ ],
+ "y",
+ "otherwise_other"
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/coalesce.a.json b/test/fixtures/expression_equality/coalesce.a.json
new file mode 100644
index 0000000000..8fae579e7c
--- /dev/null
+++ b/test/fixtures/expression_equality/coalesce.a.json
@@ -0,0 +1,16 @@
+[
+ "coalesce",
+ [
+ "get",
+ "x"
+ ],
+ [
+ "get",
+ "y"
+ ],
+ [
+ "get",
+ "z"
+ ],
+ 0
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/coalesce.b.json b/test/fixtures/expression_equality/coalesce.b.json
new file mode 100644
index 0000000000..4e0af8baa0
--- /dev/null
+++ b/test/fixtures/expression_equality/coalesce.b.json
@@ -0,0 +1,16 @@
+[
+ "coalesce",
+ [
+ "get",
+ "x"
+ ],
+ [
+ "get",
+ "y"
+ ],
+ [
+ "get",
+ "z"
+ ],
+ 1
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/concat.a.json b/test/fixtures/expression_equality/concat.a.json
new file mode 100644
index 0000000000..08c95d7f49
--- /dev/null
+++ b/test/fixtures/expression_equality/concat.a.json
@@ -0,0 +1,6 @@
+[
+ "concat",
+ "a",
+ "b",
+ "c"
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/concat.b.json b/test/fixtures/expression_equality/concat.b.json
new file mode 100644
index 0000000000..e3396d4fc0
--- /dev/null
+++ b/test/fixtures/expression_equality/concat.b.json
@@ -0,0 +1,6 @@
+[
+ "concat",
+ "a",
+ "b",
+ "c_other"
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/cos.a.json b/test/fixtures/expression_equality/cos.a.json
new file mode 100644
index 0000000000..e41430de53
--- /dev/null
+++ b/test/fixtures/expression_equality/cos.a.json
@@ -0,0 +1,4 @@
+[
+ "cos",
+ 0
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/cos.b.json b/test/fixtures/expression_equality/cos.b.json
new file mode 100644
index 0000000000..5ba4424dae
--- /dev/null
+++ b/test/fixtures/expression_equality/cos.b.json
@@ -0,0 +1,4 @@
+[
+ "cos",
+ 1
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/divide.a.json b/test/fixtures/expression_equality/divide.a.json
new file mode 100644
index 0000000000..40a67a871c
--- /dev/null
+++ b/test/fixtures/expression_equality/divide.a.json
@@ -0,0 +1,5 @@
+[
+ "/",
+ 10,
+ 5
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/divide.b.json b/test/fixtures/expression_equality/divide.b.json
new file mode 100644
index 0000000000..e3f7b155b2
--- /dev/null
+++ b/test/fixtures/expression_equality/divide.b.json
@@ -0,0 +1,5 @@
+[
+ "/",
+ 10,
+ 6
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/downcase.a.json b/test/fixtures/expression_equality/downcase.a.json
new file mode 100644
index 0000000000..ca367218c4
--- /dev/null
+++ b/test/fixtures/expression_equality/downcase.a.json
@@ -0,0 +1,4 @@
+[
+ "downcase",
+ "StRiNg"
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/downcase.b.json b/test/fixtures/expression_equality/downcase.b.json
new file mode 100644
index 0000000000..fd9ea9881d
--- /dev/null
+++ b/test/fixtures/expression_equality/downcase.b.json
@@ -0,0 +1,4 @@
+[
+ "downcase",
+ "StRiNg_other"
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/get.a.json b/test/fixtures/expression_equality/get.a.json
new file mode 100644
index 0000000000..57c3df48e7
--- /dev/null
+++ b/test/fixtures/expression_equality/get.a.json
@@ -0,0 +1,7 @@
+[
+ "number",
+ [
+ "get",
+ "x"
+ ]
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/get.b.json b/test/fixtures/expression_equality/get.b.json
new file mode 100644
index 0000000000..d1843362d3
--- /dev/null
+++ b/test/fixtures/expression_equality/get.b.json
@@ -0,0 +1,7 @@
+[
+ "number",
+ [
+ "get",
+ "x_other"
+ ]
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/has.a.json b/test/fixtures/expression_equality/has.a.json
new file mode 100644
index 0000000000..8326754107
--- /dev/null
+++ b/test/fixtures/expression_equality/has.a.json
@@ -0,0 +1,4 @@
+[
+ "has",
+ "x"
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/has.b.json b/test/fixtures/expression_equality/has.b.json
new file mode 100644
index 0000000000..20b6072303
--- /dev/null
+++ b/test/fixtures/expression_equality/has.b.json
@@ -0,0 +1,4 @@
+[
+ "has",
+ "x_other"
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/heatmap-density.a.json b/test/fixtures/expression_equality/heatmap-density.a.json
new file mode 100644
index 0000000000..90bd396f54
--- /dev/null
+++ b/test/fixtures/expression_equality/heatmap-density.a.json
@@ -0,0 +1,23 @@
+[
+ "interpolate",
+ [
+ "linear"
+ ],
+ [
+ "heatmap-density"
+ ],
+ 0,
+ [
+ "rgb",
+ 0,
+ 0,
+ 255
+ ],
+ 1,
+ [
+ "rgb",
+ 255,
+ 0,
+ 0
+ ]
+]
diff --git a/test/fixtures/expression_equality/heatmap-density.b.json b/test/fixtures/expression_equality/heatmap-density.b.json
new file mode 100644
index 0000000000..bce8ab03a1
--- /dev/null
+++ b/test/fixtures/expression_equality/heatmap-density.b.json
@@ -0,0 +1,23 @@
+[
+ "interpolate",
+ [
+ "linear"
+ ],
+ [
+ "heatmap-density"
+ ],
+ 0,
+ [
+ "rgb",
+ 0,
+ 0,
+ 255
+ ],
+ 1,
+ [
+ "rgb",
+ 255,
+ 255,
+ 255
+ ]
+]
diff --git a/test/fixtures/expression_equality/let.a.json b/test/fixtures/expression_equality/let.a.json
new file mode 100644
index 0000000000..fb24e50cfb
--- /dev/null
+++ b/test/fixtures/expression_equality/let.a.json
@@ -0,0 +1,25 @@
+[
+ "let",
+ "a",
+ 1,
+ "b",
+ 2,
+ [
+ "+",
+ [
+ "+",
+ [
+ "var",
+ "a"
+ ],
+ [
+ "var",
+ "b"
+ ]
+ ],
+ [
+ "var",
+ "a"
+ ]
+ ]
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/let.b.json b/test/fixtures/expression_equality/let.b.json
new file mode 100644
index 0000000000..26813cb6ff
--- /dev/null
+++ b/test/fixtures/expression_equality/let.b.json
@@ -0,0 +1,25 @@
+[
+ "let",
+ "a",
+ 1,
+ "b",
+ 3,
+ [
+ "+",
+ [
+ "+",
+ [
+ "var",
+ "a"
+ ],
+ [
+ "var",
+ "b"
+ ]
+ ],
+ [
+ "var",
+ "b"
+ ]
+ ]
+]
diff --git a/test/fixtures/expression_equality/ln.a.json b/test/fixtures/expression_equality/ln.a.json
new file mode 100644
index 0000000000..30d80f36ae
--- /dev/null
+++ b/test/fixtures/expression_equality/ln.a.json
@@ -0,0 +1,4 @@
+[
+ "ln",
+ 2
+]
diff --git a/test/fixtures/expression_equality/ln.b.json b/test/fixtures/expression_equality/ln.b.json
new file mode 100644
index 0000000000..9bc04ad586
--- /dev/null
+++ b/test/fixtures/expression_equality/ln.b.json
@@ -0,0 +1,6 @@
+[
+ "ln",
+ [
+ "e"
+ ]
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/log10.a.json b/test/fixtures/expression_equality/log10.a.json
new file mode 100644
index 0000000000..32e4c18807
--- /dev/null
+++ b/test/fixtures/expression_equality/log10.a.json
@@ -0,0 +1,4 @@
+[
+ "log10",
+ 100
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/log10.b.json b/test/fixtures/expression_equality/log10.b.json
new file mode 100644
index 0000000000..8f32c204f9
--- /dev/null
+++ b/test/fixtures/expression_equality/log10.b.json
@@ -0,0 +1,4 @@
+[
+ "log10",
+ 101
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/log2.a.json b/test/fixtures/expression_equality/log2.a.json
new file mode 100644
index 0000000000..95cdc15373
--- /dev/null
+++ b/test/fixtures/expression_equality/log2.a.json
@@ -0,0 +1,4 @@
+[
+ "log2",
+ 1024
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/log2.b.json b/test/fixtures/expression_equality/log2.b.json
new file mode 100644
index 0000000000..2fffaeb32a
--- /dev/null
+++ b/test/fixtures/expression_equality/log2.b.json
@@ -0,0 +1,4 @@
+[
+ "log2",
+ 1025
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/match.a.json b/test/fixtures/expression_equality/match.a.json
new file mode 100644
index 0000000000..ba8afc4126
--- /dev/null
+++ b/test/fixtures/expression_equality/match.a.json
@@ -0,0 +1,12 @@
+[
+ "match",
+ [
+ "get",
+ "x"
+ ],
+ "a",
+ "Apple",
+ "b",
+ "Banana",
+ "Kumquat"
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/match.b.json b/test/fixtures/expression_equality/match.b.json
new file mode 100644
index 0000000000..2404b8e2e7
--- /dev/null
+++ b/test/fixtures/expression_equality/match.b.json
@@ -0,0 +1,12 @@
+[
+ "match",
+ [
+ "get",
+ "x"
+ ],
+ "a",
+ "Apple",
+ "b",
+ "Banana",
+ "Kumquat_other"
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/max.a.json b/test/fixtures/expression_equality/max.a.json
new file mode 100644
index 0000000000..09a8f82bd7
--- /dev/null
+++ b/test/fixtures/expression_equality/max.a.json
@@ -0,0 +1,6 @@
+[
+ "max",
+ 0,
+ -1,
+ 100
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/max.b.json b/test/fixtures/expression_equality/max.b.json
new file mode 100644
index 0000000000..1b0beb20d6
--- /dev/null
+++ b/test/fixtures/expression_equality/max.b.json
@@ -0,0 +1,6 @@
+[
+ "max",
+ 0,
+ -1,
+ 101
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/min.a.json b/test/fixtures/expression_equality/min.a.json
new file mode 100644
index 0000000000..38cc90f1cd
--- /dev/null
+++ b/test/fixtures/expression_equality/min.a.json
@@ -0,0 +1,5 @@
+[
+ "min",
+ ["get", "x"],
+ 0
+]
diff --git a/test/fixtures/expression_equality/min.b.json b/test/fixtures/expression_equality/min.b.json
new file mode 100644
index 0000000000..84a5f66842
--- /dev/null
+++ b/test/fixtures/expression_equality/min.b.json
@@ -0,0 +1,5 @@
+[
+ "min",
+ ["get", "x"],
+ 1
+]
diff --git a/test/fixtures/expression_equality/minus.a.json b/test/fixtures/expression_equality/minus.a.json
new file mode 100644
index 0000000000..9eb4f954e7
--- /dev/null
+++ b/test/fixtures/expression_equality/minus.a.json
@@ -0,0 +1,5 @@
+[
+ "-",
+ 5,
+ 7
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/minus.b.json b/test/fixtures/expression_equality/minus.b.json
new file mode 100644
index 0000000000..87042b98ef
--- /dev/null
+++ b/test/fixtures/expression_equality/minus.b.json
@@ -0,0 +1,5 @@
+[
+ "-",
+ 5,
+ 8
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/mod.a.json b/test/fixtures/expression_equality/mod.a.json
new file mode 100644
index 0000000000..8439bafcd1
--- /dev/null
+++ b/test/fixtures/expression_equality/mod.a.json
@@ -0,0 +1,5 @@
+[
+ "%",
+ 18,
+ 12
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/mod.b.json b/test/fixtures/expression_equality/mod.b.json
new file mode 100644
index 0000000000..362e1721c1
--- /dev/null
+++ b/test/fixtures/expression_equality/mod.b.json
@@ -0,0 +1,5 @@
+[
+ "%",
+ 18,
+ 13
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/not.a.json b/test/fixtures/expression_equality/not.a.json
new file mode 100644
index 0000000000..b5f03e0ac0
--- /dev/null
+++ b/test/fixtures/expression_equality/not.a.json
@@ -0,0 +1,10 @@
+[
+ "!",
+ [
+ "boolean",
+ [
+ "get",
+ "x"
+ ]
+ ]
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/not.b.json b/test/fixtures/expression_equality/not.b.json
new file mode 100644
index 0000000000..a4d77adf2e
--- /dev/null
+++ b/test/fixtures/expression_equality/not.b.json
@@ -0,0 +1,10 @@
+[
+ "!",
+ [
+ "boolean",
+ [
+ "get",
+ "x_other"
+ ]
+ ]
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/number.a.json b/test/fixtures/expression_equality/number.a.json
new file mode 100644
index 0000000000..57c3df48e7
--- /dev/null
+++ b/test/fixtures/expression_equality/number.a.json
@@ -0,0 +1,7 @@
+[
+ "number",
+ [
+ "get",
+ "x"
+ ]
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/number.b.json b/test/fixtures/expression_equality/number.b.json
new file mode 100644
index 0000000000..d1843362d3
--- /dev/null
+++ b/test/fixtures/expression_equality/number.b.json
@@ -0,0 +1,7 @@
+[
+ "number",
+ [
+ "get",
+ "x_other"
+ ]
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/object.a.json b/test/fixtures/expression_equality/object.a.json
new file mode 100644
index 0000000000..7551cfdbb2
--- /dev/null
+++ b/test/fixtures/expression_equality/object.a.json
@@ -0,0 +1,7 @@
+[
+ "object",
+ [
+ "get",
+ "x"
+ ]
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/object.b.json b/test/fixtures/expression_equality/object.b.json
new file mode 100644
index 0000000000..8444d40c0e
--- /dev/null
+++ b/test/fixtures/expression_equality/object.b.json
@@ -0,0 +1,7 @@
+[
+ "object",
+ [
+ "get",
+ "x_other"
+ ]
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/plus.a.json b/test/fixtures/expression_equality/plus.a.json
new file mode 100644
index 0000000000..a00c4409fa
--- /dev/null
+++ b/test/fixtures/expression_equality/plus.a.json
@@ -0,0 +1,7 @@
+[
+ "+",
+ 1,
+ 2,
+ 3,
+ 4
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/plus.b.json b/test/fixtures/expression_equality/plus.b.json
new file mode 100644
index 0000000000..87c071123f
--- /dev/null
+++ b/test/fixtures/expression_equality/plus.b.json
@@ -0,0 +1,7 @@
+[
+ "+",
+ 1,
+ 2,
+ 3,
+ 5
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/pow.a.json b/test/fixtures/expression_equality/pow.a.json
new file mode 100644
index 0000000000..c1a1e67f86
--- /dev/null
+++ b/test/fixtures/expression_equality/pow.a.json
@@ -0,0 +1,11 @@
+[
+ "^",
+ 4,
+ [
+ "number",
+ [
+ "get",
+ "x"
+ ]
+ ]
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/pow.b.json b/test/fixtures/expression_equality/pow.b.json
new file mode 100644
index 0000000000..ca5331b92a
--- /dev/null
+++ b/test/fixtures/expression_equality/pow.b.json
@@ -0,0 +1,11 @@
+[
+ "^",
+ 4,
+ [
+ "number",
+ [
+ "get",
+ "x_other"
+ ]
+ ]
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/rgb.a.json b/test/fixtures/expression_equality/rgb.a.json
new file mode 100644
index 0000000000..ce6c5e5dd0
--- /dev/null
+++ b/test/fixtures/expression_equality/rgb.a.json
@@ -0,0 +1,6 @@
+[
+ "rgb",
+ 0,
+ 0,
+ 255
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/rgb.b.json b/test/fixtures/expression_equality/rgb.b.json
new file mode 100644
index 0000000000..577c19748b
--- /dev/null
+++ b/test/fixtures/expression_equality/rgb.b.json
@@ -0,0 +1,6 @@
+[
+ "rgb",
+ 0,
+ 0,
+ 0
+]
diff --git a/test/fixtures/expression_equality/rgba.a.json b/test/fixtures/expression_equality/rgba.a.json
new file mode 100644
index 0000000000..e8ad7326c1
--- /dev/null
+++ b/test/fixtures/expression_equality/rgba.a.json
@@ -0,0 +1,7 @@
+[
+ "rgba",
+ 0,
+ 0,
+ 255,
+ 1
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/rgba.b.json b/test/fixtures/expression_equality/rgba.b.json
new file mode 100644
index 0000000000..81d442eaae
--- /dev/null
+++ b/test/fixtures/expression_equality/rgba.b.json
@@ -0,0 +1,7 @@
+[
+ "rgba",
+ 0,
+ 0,
+ 255,
+ 0.5
+]
diff --git a/test/fixtures/expression_equality/sin.a.json b/test/fixtures/expression_equality/sin.a.json
new file mode 100644
index 0000000000..0f7ae2966f
--- /dev/null
+++ b/test/fixtures/expression_equality/sin.a.json
@@ -0,0 +1,4 @@
+[
+ "sin",
+ 0
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/sin.b.json b/test/fixtures/expression_equality/sin.b.json
new file mode 100644
index 0000000000..90f309b80f
--- /dev/null
+++ b/test/fixtures/expression_equality/sin.b.json
@@ -0,0 +1,4 @@
+[
+ "sin",
+ 1
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/sqrt.a.json b/test/fixtures/expression_equality/sqrt.a.json
new file mode 100644
index 0000000000..56dd85bc1a
--- /dev/null
+++ b/test/fixtures/expression_equality/sqrt.a.json
@@ -0,0 +1,7 @@
+[
+ "sqrt",
+ [
+ "get",
+ "x"
+ ]
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/sqrt.b.json b/test/fixtures/expression_equality/sqrt.b.json
new file mode 100644
index 0000000000..ab05d5084c
--- /dev/null
+++ b/test/fixtures/expression_equality/sqrt.b.json
@@ -0,0 +1,7 @@
+[
+ "sqrt",
+ [
+ "get",
+ "x_other"
+ ]
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/step.a.json b/test/fixtures/expression_equality/step.a.json
new file mode 100644
index 0000000000..4fee85cd03
--- /dev/null
+++ b/test/fixtures/expression_equality/step.a.json
@@ -0,0 +1,18 @@
+[
+ "number",
+ [
+ "step",
+ [
+ "number",
+ [
+ "get",
+ "x"
+ ]
+ ],
+ 11,
+ 0,
+ 111,
+ 1,
+ 1111
+ ]
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/step.b.json b/test/fixtures/expression_equality/step.b.json
new file mode 100644
index 0000000000..0a591a84df
--- /dev/null
+++ b/test/fixtures/expression_equality/step.b.json
@@ -0,0 +1,18 @@
+[
+ "number",
+ [
+ "step",
+ [
+ "number",
+ [
+ "get",
+ "x"
+ ]
+ ],
+ 11,
+ 0,
+ 111,
+ 1,
+ 1112
+ ]
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/string.a.json b/test/fixtures/expression_equality/string.a.json
new file mode 100644
index 0000000000..a79344f338
--- /dev/null
+++ b/test/fixtures/expression_equality/string.a.json
@@ -0,0 +1,7 @@
+[
+ "string",
+ [
+ "get",
+ "x"
+ ]
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/string.b.json b/test/fixtures/expression_equality/string.b.json
new file mode 100644
index 0000000000..6f77f3c3cf
--- /dev/null
+++ b/test/fixtures/expression_equality/string.b.json
@@ -0,0 +1,7 @@
+[
+ "string",
+ [
+ "get",
+ "x_other"
+ ]
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/tan.a.json b/test/fixtures/expression_equality/tan.a.json
new file mode 100644
index 0000000000..c78e47e492
--- /dev/null
+++ b/test/fixtures/expression_equality/tan.a.json
@@ -0,0 +1,4 @@
+[
+ "tan",
+ 0.7853981633974483
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/tan.b.json b/test/fixtures/expression_equality/tan.b.json
new file mode 100644
index 0000000000..c22e64cf8a
--- /dev/null
+++ b/test/fixtures/expression_equality/tan.b.json
@@ -0,0 +1,4 @@
+[
+ "tan",
+ 1.7853981633974483
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/times.a.json b/test/fixtures/expression_equality/times.a.json
new file mode 100644
index 0000000000..ce6d9b46e0
--- /dev/null
+++ b/test/fixtures/expression_equality/times.a.json
@@ -0,0 +1,7 @@
+[
+ "*",
+ 3,
+ 2,
+ 0.5,
+ 2
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/times.b.json b/test/fixtures/expression_equality/times.b.json
new file mode 100644
index 0000000000..147e011172
--- /dev/null
+++ b/test/fixtures/expression_equality/times.b.json
@@ -0,0 +1,7 @@
+[
+ "*",
+ 3,
+ 2,
+ 0.5,
+ 3
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/to-boolean.a.json b/test/fixtures/expression_equality/to-boolean.a.json
new file mode 100644
index 0000000000..ccf48149ec
--- /dev/null
+++ b/test/fixtures/expression_equality/to-boolean.a.json
@@ -0,0 +1,7 @@
+[
+ "to-boolean",
+ [
+ "get",
+ "x"
+ ]
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/to-boolean.b.json b/test/fixtures/expression_equality/to-boolean.b.json
new file mode 100644
index 0000000000..7896261241
--- /dev/null
+++ b/test/fixtures/expression_equality/to-boolean.b.json
@@ -0,0 +1,7 @@
+[
+ "to-boolean",
+ [
+ "get",
+ "x_other"
+ ]
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/to-color.a.json b/test/fixtures/expression_equality/to-color.a.json
new file mode 100644
index 0000000000..de9ab59eec
--- /dev/null
+++ b/test/fixtures/expression_equality/to-color.a.json
@@ -0,0 +1,7 @@
+[
+ "to-color",
+ [
+ "get",
+ "x"
+ ]
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/to-color.b.json b/test/fixtures/expression_equality/to-color.b.json
new file mode 100644
index 0000000000..c0566ef6a7
--- /dev/null
+++ b/test/fixtures/expression_equality/to-color.b.json
@@ -0,0 +1,7 @@
+[
+ "to-color",
+ [
+ "get",
+ "x_other"
+ ]
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/to-number.a.json b/test/fixtures/expression_equality/to-number.a.json
new file mode 100644
index 0000000000..65b2df5014
--- /dev/null
+++ b/test/fixtures/expression_equality/to-number.a.json
@@ -0,0 +1,7 @@
+[
+ "to-number",
+ [
+ "get",
+ "x"
+ ]
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/to-number.b.json b/test/fixtures/expression_equality/to-number.b.json
new file mode 100644
index 0000000000..b38dc5a455
--- /dev/null
+++ b/test/fixtures/expression_equality/to-number.b.json
@@ -0,0 +1,7 @@
+[
+ "to-number",
+ [
+ "get",
+ "x_other"
+ ]
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/to-string.a.json b/test/fixtures/expression_equality/to-string.a.json
new file mode 100644
index 0000000000..66f9a9caa1
--- /dev/null
+++ b/test/fixtures/expression_equality/to-string.a.json
@@ -0,0 +1,7 @@
+[
+ "to-string",
+ [
+ "get",
+ "x"
+ ]
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/to-string.b.json b/test/fixtures/expression_equality/to-string.b.json
new file mode 100644
index 0000000000..977a9d7769
--- /dev/null
+++ b/test/fixtures/expression_equality/to-string.b.json
@@ -0,0 +1,7 @@
+[
+ "to-string",
+ [
+ "get",
+ "x_other"
+ ]
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/typeof.a.json b/test/fixtures/expression_equality/typeof.a.json
new file mode 100644
index 0000000000..7843ff8c7f
--- /dev/null
+++ b/test/fixtures/expression_equality/typeof.a.json
@@ -0,0 +1,7 @@
+[
+ "typeof",
+ [
+ "get",
+ "x"
+ ]
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/typeof.b.json b/test/fixtures/expression_equality/typeof.b.json
new file mode 100644
index 0000000000..412482347a
--- /dev/null
+++ b/test/fixtures/expression_equality/typeof.b.json
@@ -0,0 +1,7 @@
+[
+ "typeof",
+ [
+ "get",
+ "x_other"
+ ]
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/upcase.a.json b/test/fixtures/expression_equality/upcase.a.json
new file mode 100644
index 0000000000..d12ca7b08d
--- /dev/null
+++ b/test/fixtures/expression_equality/upcase.a.json
@@ -0,0 +1,4 @@
+[
+ "upcase",
+ "string"
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/upcase.b.json b/test/fixtures/expression_equality/upcase.b.json
new file mode 100644
index 0000000000..ddeeb0300c
--- /dev/null
+++ b/test/fixtures/expression_equality/upcase.b.json
@@ -0,0 +1,4 @@
+[
+ "upcase",
+ "string_other"
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/zoom.a.json b/test/fixtures/expression_equality/zoom.a.json
new file mode 100644
index 0000000000..fc675721ab
--- /dev/null
+++ b/test/fixtures/expression_equality/zoom.a.json
@@ -0,0 +1,13 @@
+[
+ "interpolate",
+ [
+ "linear"
+ ],
+ [
+ "zoom"
+ ],
+ 0,
+ 0,
+ 30,
+ 30
+] \ No newline at end of file
diff --git a/test/fixtures/expression_equality/zoom.b.json b/test/fixtures/expression_equality/zoom.b.json
new file mode 100644
index 0000000000..6314858a5e
--- /dev/null
+++ b/test/fixtures/expression_equality/zoom.b.json
@@ -0,0 +1,13 @@
+[
+ "interpolate",
+ [
+ "linear"
+ ],
+ [
+ "zoom"
+ ],
+ 0,
+ 0,
+ 30,
+ 31
+] \ No newline at end of file
diff --git a/test/fixtures/shared_context/expected.png b/test/fixtures/shared_context/expected.png
new file mode 100644
index 0000000000..3b3fd0e315
--- /dev/null
+++ b/test/fixtures/shared_context/expected.png
Binary files differ
diff --git a/test/fixtures/style_parser/expressions.info.json b/test/fixtures/style_parser/expressions.info.json
new file mode 100644
index 0000000000..9e1765ecd0
--- /dev/null
+++ b/test/fixtures/style_parser/expressions.info.json
@@ -0,0 +1,12 @@
+{
+ "default": {
+ "log": [
+ [1, "WARNING", "ParseStyle", "Expected color but found number instead."],
+ [1, "WARNING", "ParseStyle", "[2]: Expected number but found string instead."],
+ [1, "WARNING", "ParseStyle", "\"zoom\" expression may only be used as input to a top-level \"step\" or \"interpolate\" expression."],
+ [1, "WARNING", "ParseStyle", "value must be a string"],
+ [1, "WARNING", "ParseStyle", "property expressions not supported"],
+ [1, "WARNING", "ParseStyle", "Type array<number> is not interpolatable."]
+ ]
+ }
+}
diff --git a/test/fixtures/style_parser/expressions.style.json b/test/fixtures/style_parser/expressions.style.json
new file mode 100644
index 0000000000..b9b4aeac7f
--- /dev/null
+++ b/test/fixtures/style_parser/expressions.style.json
@@ -0,0 +1,74 @@
+{
+ "version": 8,
+ "sources": {
+ "source": {
+ "type": "vector",
+ "url": "mapbox://mapbox.mapbox-streets-v5"
+ }
+ },
+ "layers": [
+ {
+ "id": "valid expression",
+ "type": "fill",
+ "source": "source",
+ "source-layer": "layer",
+ "paint": {
+ "fill-color": ["rgba", 10, ["number", ["get", "x"]], 30, 1]
+ }
+ },
+ {
+ "id": "invalid expression type - color",
+ "type": "fill",
+ "source": "source",
+ "source-layer": "layer",
+ "paint": {
+ "fill-color": ["pi"]
+ }
+ },
+ {
+ "id": "invalid expression - fails type checking",
+ "type": "fill",
+ "source": "source",
+ "source-layer": "layer",
+ "paint": {
+ "fill-color": ["rgba", 1, "should be a number", 0, 1]
+ }
+ },
+ {
+ "id": "invalid expression - nested zoom expression",
+ "type": "fill",
+ "source": "source",
+ "source-layer": "layer",
+ "paint": {
+ "fill-opacity": ["+", 0.5, ["interpolate", ["linear"], ["zoom"], 0, 0, 1, 1]]
+ }
+ },
+ {
+ "id": "invalid expression - not allowed in visibility",
+ "type": "fill",
+ "source": "source",
+ "source-layer": "layer",
+ "layout": {
+ "visibility": ["literal", true]
+ }
+ },
+ {
+ "id": "invalid expression - not a DDS property",
+ "type": "fill-extrusion",
+ "source": "source",
+ "source-layer": "layer",
+ "paint": {
+ "fill-extrusion-opacity": ["get", "opacity"]
+ }
+ },
+ {
+ "id": "invalid expression - line-dasharray must use step interpolation",
+ "type": "line",
+ "source": "source",
+ "source-layer": "layer",
+ "paint": {
+ "line-dasharray": ["interpolate", ["linear"], ["zoom"], 0, ["literal", [1, 2]], 1, ["literal", [3, 4]]]
+ }
+ }
+ ]
+}
diff --git a/test/gl/bucket.test.cpp b/test/gl/bucket.test.cpp
index 5f9626bc99..0e73dfb28f 100644
--- a/test/gl/bucket.test.cpp
+++ b/test/gl/bucket.test.cpp
@@ -1,6 +1,7 @@
#include <mbgl/test/util.hpp>
#include <mbgl/test/stub_geometry_tile_feature.hpp>
+#include <mbgl/renderer/backend_scope.hpp>
#include <mbgl/renderer/buckets/circle_bucket.hpp>
#include <mbgl/renderer/buckets/fill_bucket.hpp>
#include <mbgl/renderer/buckets/line_bucket.hpp>
@@ -9,6 +10,7 @@
#include <mbgl/renderer/bucket_parameters.hpp>
#include <mbgl/style/layers/symbol_layer_properties.hpp>
#include <mbgl/gl/context.hpp>
+#include <mbgl/gl/headless_backend.hpp>
#include <mbgl/map/mode.hpp>
@@ -41,6 +43,9 @@ PropertyMap properties;
} // namespace
TEST(Buckets, CircleBucket) {
+ HeadlessBackend backend({ 512, 256 });
+ BackendScope scope { backend };
+
gl::Context context;
CircleBucket bucket { { {0, 0, 0}, MapMode::Still, 1.0 }, {} };
ASSERT_FALSE(bucket.hasData());
@@ -57,6 +62,9 @@ TEST(Buckets, CircleBucket) {
}
TEST(Buckets, FillBucket) {
+ HeadlessBackend backend({ 512, 256 });
+ BackendScope scope { backend };
+
gl::Context context;
FillBucket bucket { { {0, 0, 0}, MapMode::Still, 1.0 }, {} };
ASSERT_FALSE(bucket.hasData());
@@ -72,6 +80,9 @@ TEST(Buckets, FillBucket) {
}
TEST(Buckets, LineBucket) {
+ HeadlessBackend backend({ 512, 256 });
+ BackendScope scope { backend };
+
gl::Context context;
LineBucket bucket { { {0, 0, 0}, MapMode::Still, 1.0 }, {}, {} };
ASSERT_FALSE(bucket.hasData());
@@ -92,6 +103,9 @@ TEST(Buckets, LineBucket) {
}
TEST(Buckets, SymbolBucket) {
+ HeadlessBackend backend({ 512, 256 });
+ BackendScope scope { backend };
+
style::SymbolLayoutProperties::PossiblyEvaluated layout;
bool sdfIcons = false;
bool iconsNeedLinear = false;
@@ -120,6 +134,9 @@ TEST(Buckets, SymbolBucket) {
}
TEST(Buckets, RasterBucket) {
+ HeadlessBackend backend({ 512, 256 });
+ BackendScope scope { backend };
+
gl::Context context;
PremultipliedImage rgba({ 1, 1 });
diff --git a/test/gl/context.test.cpp b/test/gl/context.test.cpp
new file mode 100644
index 0000000000..19e8ad5928
--- /dev/null
+++ b/test/gl/context.test.cpp
@@ -0,0 +1,114 @@
+#include <mbgl/test/util.hpp>
+
+#include <mbgl/gl/gl.hpp>
+#include <mbgl/gl/context.hpp>
+#include <mbgl/map/map.hpp>
+#include <mbgl/util/default_thread_pool.hpp>
+#include <mbgl/storage/default_file_source.hpp>
+#include <mbgl/gl/headless_frontend.hpp>
+#include <mbgl/style/style.hpp>
+#include <mbgl/style/layers/custom_layer.hpp>
+#include <mbgl/style/layers/fill_layer.hpp>
+#include <mbgl/style/layers/background_layer.hpp>
+#include <mbgl/util/io.hpp>
+#include <mbgl/util/mat4.hpp>
+#include <mbgl/util/run_loop.hpp>
+
+using namespace mbgl;
+using namespace mbgl::style;
+
+static const GLchar* vertexShaderSource = R"MBGL_SHADER(
+#ifdef GL_ES
+precision mediump float;
+#endif
+attribute vec2 a_pos;
+void main() {
+ gl_Position = vec4(a_pos, 0, 1);
+}
+)MBGL_SHADER";
+
+static const GLchar* fragmentShaderSource = R"MBGL_SHADER(
+#ifdef GL_ES
+precision mediump float;
+#endif
+void main() {
+ gl_FragColor = vec4(0, 1, 0, 1);
+}
+)MBGL_SHADER";
+
+struct Shader {
+ Shader(const GLchar* vertex, const GLchar* fragment) {
+ program = MBGL_CHECK_ERROR(glCreateProgram());
+ vertexShader = MBGL_CHECK_ERROR(glCreateShader(GL_VERTEX_SHADER));
+ fragmentShader = MBGL_CHECK_ERROR(glCreateShader(GL_FRAGMENT_SHADER));
+ MBGL_CHECK_ERROR(glShaderSource(vertexShader, 1, &vertex, nullptr));
+ MBGL_CHECK_ERROR(glCompileShader(vertexShader));
+ MBGL_CHECK_ERROR(glAttachShader(program, vertexShader));
+ MBGL_CHECK_ERROR(glShaderSource(fragmentShader, 1, &fragment, nullptr));
+ MBGL_CHECK_ERROR(glCompileShader(fragmentShader));
+ MBGL_CHECK_ERROR(glAttachShader(program, fragmentShader));
+ MBGL_CHECK_ERROR(glLinkProgram(program));
+ a_pos = MBGL_CHECK_ERROR(glGetAttribLocation(program, "a_pos"));
+ }
+
+ ~Shader() {
+ MBGL_CHECK_ERROR(glDetachShader(program, vertexShader));
+ MBGL_CHECK_ERROR(glDetachShader(program, fragmentShader));
+ MBGL_CHECK_ERROR(glDeleteShader(vertexShader));
+ MBGL_CHECK_ERROR(glDeleteShader(fragmentShader));
+ MBGL_CHECK_ERROR(glDeleteProgram(program));
+ }
+
+ GLuint program = 0;
+ GLuint vertexShader = 0;
+ GLuint fragmentShader = 0;
+ GLuint a_pos = 0;
+};
+
+struct Buffer {
+ Buffer(std::vector<GLfloat> data) {
+ MBGL_CHECK_ERROR(glGenBuffers(1, &buffer));
+ MBGL_CHECK_ERROR(glBindBuffer(GL_ARRAY_BUFFER, buffer));
+ MBGL_CHECK_ERROR(glBufferData(GL_ARRAY_BUFFER, data.size() * sizeof(GLfloat), data.data(),
+ GL_STATIC_DRAW));
+ }
+
+ ~Buffer() {
+ MBGL_CHECK_ERROR(glDeleteBuffers(1, &buffer));
+ }
+
+ GLuint buffer = 0;
+};
+
+TEST(GLContextMode, Shared) {
+ util::RunLoop loop;
+
+ DefaultFileSource fileSource(":memory:", "test/fixtures/api/assets");
+ ThreadPool threadPool(4);
+ float pixelRatio { 1 };
+
+ HeadlessFrontend frontend { pixelRatio, fileSource, threadPool, {}, GLContextMode::Shared };
+
+ Map map(frontend, MapObserver::nullObserver(), frontend.getSize(), pixelRatio, fileSource, threadPool, MapMode::Still);
+ map.getStyle().loadJSON(util::read_file("test/fixtures/api/water.json"));
+ map.setLatLngZoom({ 37.8, -122.5 }, 10);
+
+ // Set transparent background layer.
+ map.getStyle().getLayer("background")->as<BackgroundLayer>()->setBackgroundColor( { { 1.0f, 0.0f, 0.0f, 0.5f } } );
+
+ {
+ // Custom rendering outside of GL Native render loop.
+ BackendScope scope { *frontend.getBackend() };
+ frontend.getBackend()->bind();
+
+ Shader paintShader(vertexShaderSource, fragmentShaderSource);
+ Buffer triangleBuffer({ 0, 0.5, 0.5, -0.5, -0.5, -0.5 });
+ MBGL_CHECK_ERROR(glUseProgram(paintShader.program));
+ MBGL_CHECK_ERROR(glBindBuffer(GL_ARRAY_BUFFER, triangleBuffer.buffer));
+ MBGL_CHECK_ERROR(glEnableVertexAttribArray(paintShader.a_pos));
+ MBGL_CHECK_ERROR(glVertexAttribPointer(paintShader.a_pos, 2, GL_FLOAT, GL_FALSE, 0, nullptr));
+ MBGL_CHECK_ERROR(glDrawArrays(GL_TRIANGLE_STRIP, 0, 3));
+ }
+
+ test::checkImage("test/fixtures/shared_context", frontend.render(map), 0.5, 0.1);
+}
diff --git a/test/src/mbgl/test/conversion_stubs.hpp b/test/src/mbgl/test/conversion_stubs.hpp
deleted file mode 100644
index 30395ddb97..0000000000
--- a/test/src/mbgl/test/conversion_stubs.hpp
+++ /dev/null
@@ -1,124 +0,0 @@
-#pragma once
-
-#include <mbgl/style/conversion.hpp>
-#include <mbgl/util/feature.hpp>
-#include <mbgl/util/optional.hpp>
-#include <mbgl/util/variant.hpp>
-
-#include <string>
-#include <unordered_map>
-
-namespace mbgl {
-namespace style {
-namespace conversion {
-
-class Value;
-using ValueMap = std::unordered_map<std::string, Value>;
-using ValueVector = std::vector<Value>;
-class Value : public mbgl::variant<std::string,
- float,
- double,
- bool,
- mapbox::util::recursive_wrapper<ValueMap>,
- mapbox::util::recursive_wrapper<ValueVector>> {
- using variant::variant;
-};
-
-inline bool isUndefined(const Value&) {
- // Variant is always intialized
- return false;
-}
-
-inline bool isArray(const Value& value) {
- return value.is<mapbox::util::recursive_wrapper<ValueVector>>();
-}
-
-inline std::size_t arrayLength(const Value& value) {
- return value.get<mapbox::util::recursive_wrapper<ValueVector>>().get().size();
-}
-
-inline Value arrayMember(const Value& value, std::size_t i) {
- return value.get<mapbox::util::recursive_wrapper<ValueVector>>().get()[i];
-}
-
-inline bool isObject(const Value& value) {
- return value.is<mapbox::util::recursive_wrapper<ValueMap>>();
-}
-
-inline optional<Value> objectMember(const Value& value, const char* key) {
- auto map = value.get<ValueMap>();
- auto iter = map.find(key);
-
- if (iter != map.end()) {
- return iter->second;
- } else {
- return {};
- }
-}
-
-using EachMemberFn = std::function<optional<Error>(const std::string&, const Value&)>;
-
-optional<Error> eachMember(const Value& value, EachMemberFn&& fn) {
- auto map = value.get<ValueMap>();
- auto iter = map.begin();
-
- while (iter != map.end()) {
- optional<Error> result = fn(iter->first, iter->second);
- if (result) {
- return result;
- }
-
- ++iter;
- }
-
- return {};
-}
-
-inline optional<bool> toBool(const Value& value) {
- if (value.is<bool>()) {
- return value.get<bool>();
- } else {
- return {};
- }
-}
-
-inline optional<float> toNumber(const Value& value) {
- if (value.is<float>()) {
- return value.get<float>();
- } else {
- return {};
- }
- return {};
-}
-
-
-inline optional<double> toDouble(const Value& value) {
- if (value.is<double>()) {
- return value.get<double>();
- }
- return {};
-}
-
-inline optional<std::string> toString(const Value& value) {
- if (value.is<std::string>()) {
- return value.get<std::string>();
- } else {
- return {};
- }
-}
-
-inline optional<mbgl::Value> toValue(const Value& value) {
- if (value.is<bool>()) {
- return { value.get<bool>() };
- } else if (value.is<std::string>()) {
- return { value.get<std::string>() };
- } else if (value.is<float>()) {
- return { double(value.get<float>()) };
- } else {
- return {};
- }
-}
-
-} // namespace conversion
-} // namespace style
-} // namespace mbgl
diff --git a/test/src/mbgl/test/stub_file_source.cpp b/test/src/mbgl/test/stub_file_source.cpp
index 7891d5d907..0bbff84ff3 100644
--- a/test/src/mbgl/test/stub_file_source.cpp
+++ b/test/src/mbgl/test/stub_file_source.cpp
@@ -17,7 +17,12 @@ public:
StubFileSource& fileSource;
};
-StubFileSource::StubFileSource() {
+StubFileSource::StubFileSource(ResponseType type_)
+ : type(type_) {
+ if (type == ResponseType::Synchronous) {
+ return;
+ }
+
timer.start(1ms, 1ms, [this] {
// Explicit copy to avoid iterator invalidation if ~StubFileRequest gets called within the loop.
auto pending_ = pending;
@@ -46,7 +51,14 @@ StubFileSource::~StubFileSource() = default;
std::unique_ptr<AsyncRequest> StubFileSource::request(const Resource& resource, Callback callback) {
auto req = std::make_unique<StubFileRequest>(*this);
- pending.emplace(req.get(), std::make_tuple(resource, response, callback));
+ if (type == ResponseType::Synchronous) {
+ optional<Response> res = response(resource);
+ if (res) {
+ callback(*res);
+ }
+ } else {
+ pending.emplace(req.get(), std::make_tuple(resource, response, callback));
+ }
return std::move(req);
}
diff --git a/test/src/mbgl/test/stub_file_source.hpp b/test/src/mbgl/test/stub_file_source.hpp
index 85118e1a77..6cee8377c6 100644
--- a/test/src/mbgl/test/stub_file_source.hpp
+++ b/test/src/mbgl/test/stub_file_source.hpp
@@ -9,7 +9,12 @@ namespace mbgl {
class StubFileSource : public FileSource {
public:
- StubFileSource();
+ enum class ResponseType {
+ Asynchronous = 0,
+ Synchronous
+ };
+
+ StubFileSource(ResponseType = ResponseType::Asynchronous);
~StubFileSource() override;
std::unique_ptr<AsyncRequest> request(const Resource&, Callback) override;
@@ -36,6 +41,7 @@ private:
optional<Response> defaultResponse(const Resource&);
std::unordered_map<AsyncRequest*, std::tuple<Resource, ResponseFunction, Callback>> pending;
+ ResponseType type;
util::Timer timer;
};
diff --git a/test/storage/offline.test.cpp b/test/storage/offline.test.cpp
index e7ebe5199f..59aebebaba 100644
--- a/test/storage/offline.test.cpp
+++ b/test/storage/offline.test.cpp
@@ -4,6 +4,7 @@
#include <gtest/gtest.h>
using namespace mbgl;
+using SourceType = mbgl::style::SourceType;
static const LatLngBounds sanFrancisco =
LatLngBounds::hull({ 37.6609, -122.5744 }, { 37.8271, -122.3204 });
diff --git a/test/style/conversion/function.test.cpp b/test/style/conversion/function.test.cpp
index 1eff94d939..a48be2c075 100644
--- a/test/style/conversion/function.test.cpp
+++ b/test/style/conversion/function.test.cpp
@@ -1,9 +1,9 @@
#include <mbgl/test/util.hpp>
-#include <mbgl/style/conversion.hpp>
-#include <mbgl/style/rapidjson_conversion.hpp>
+#include <mbgl/style/conversion/json.hpp>
#include <mbgl/style/conversion/constant.hpp>
#include <mbgl/style/conversion/function.hpp>
+#include <mbgl/style/conversion/data_driven_property_value.hpp>
#include <mbgl/util/rapidjson.hpp>
using namespace mbgl;
@@ -13,10 +13,8 @@ using namespace mbgl::style::conversion;
TEST(StyleConversion, Function) {
Error error;
- auto parseFunction = [&](const std::string& src) {
- JSDocument doc;
- doc.Parse<0>(src);
- return convert<CameraFunction<float>, JSValue>(doc, error);
+ auto parseFunction = [&](const std::string& json) {
+ return convertJSON<CameraFunction<float>>(json, error);
};
auto fn1 = parseFunction(R"({"stops":[]})");
@@ -54,3 +52,29 @@ TEST(StyleConversion, Function) {
ASSERT_FALSE(fn9);
ASSERT_EQ("function base must be a number", error.message);
}
+
+TEST(StyleConversion, CompositeFunctionExpression) {
+ Error error;
+
+ auto parseFunction = [&](const std::string& src) {
+ JSDocument doc;
+ doc.Parse<0>(src);
+ return convert<DataDrivenPropertyValue<float>>(doc, error);
+ };
+
+ auto fn1 = parseFunction(R"(["interpolate", ["linear"], ["zoom"], 0, ["number", ["get", "x"]], 10, 10])");
+ ASSERT_TRUE(fn1);
+
+ auto fn2 = parseFunction(R"(["coalesce", ["interpolate", ["linear"], ["zoom"], 0, ["number", ["get", "x"]], 10, 10], 0])");
+ ASSERT_TRUE(fn2);
+
+ auto fn3 = parseFunction(R"(["let", "a", 0, ["interpolate", ["linear"], ["zoom"], 0, ["number", ["get", "x"]], 10, 10] ])");
+ ASSERT_TRUE(fn3);
+
+ auto fn4 = parseFunction(R"(["coalesce", ["let", "a", 0, ["interpolate", ["linear"], ["zoom"], 0, ["number", ["get", "x"]], 10, 10], 0 ])");
+ ASSERT_TRUE(fn4);
+
+ auto fn5 = parseFunction(R"(["coalesce", ["interpolate", ["linear"], ["number", ["get", "x"]], 0, ["zoom"], 10, 10], 0])");
+ ASSERT_FALSE(fn5);
+ ASSERT_EQ(R"("zoom" expression may only be used as input to a top-level "step" or "interpolate" expression.)", error.message);
+}
diff --git a/test/style/conversion/geojson_options.test.cpp b/test/style/conversion/geojson_options.test.cpp
index a798ad6559..4c5a0c9aa4 100644
--- a/test/style/conversion/geojson_options.test.cpp
+++ b/test/style/conversion/geojson_options.test.cpp
@@ -1,8 +1,7 @@
#include <mbgl/test/util.hpp>
-#include <mbgl/style/conversion.hpp>
+#include <mbgl/style/conversion/json.hpp>
#include <mbgl/style/conversion/geojson_options.hpp>
-#include <mbgl/test/conversion_stubs.hpp>
#include <mbgl/util/logging.hpp>
@@ -10,26 +9,22 @@ using namespace mbgl::style;
using namespace mbgl::style::conversion;
TEST(GeoJSONOptions, Basic) {
- ValueMap map;
- Value raw(map);
Error error;
- mbgl::optional<GeoJSONOptions> converted = convert<GeoJSONOptions>(raw, error);
+ mbgl::optional<GeoJSONOptions> converted = convertJSON<GeoJSONOptions>("{}", error);
ASSERT_TRUE((bool) converted);
}
TEST(GeoJSONOptions, ErrorHandling) {
- ValueMap map {{"maxzoom", std::string{"should not be a string"}}};
- Value raw(map);
Error error;
- mbgl::optional<GeoJSONOptions> converted = convert<GeoJSONOptions>(raw, error);
+ mbgl::optional<GeoJSONOptions> converted = convertJSON<GeoJSONOptions>(R"JSON({
+ "maxzoom": "should not be a string"
+ })JSON", error);
ASSERT_FALSE((bool) converted);
}
TEST(GeoJSONOptions, RetainsDefaults) {
- ValueMap map;
- Value raw(map);
Error error;
- GeoJSONOptions converted = *convert<GeoJSONOptions>(raw, error);
+ GeoJSONOptions converted = *convertJSON<GeoJSONOptions>("{}", error);
GeoJSONOptions defaults;
// GeoJSON-VT
@@ -44,22 +39,16 @@ TEST(GeoJSONOptions, RetainsDefaults) {
ASSERT_EQ(converted.clusterMaxZoom, defaults.clusterMaxZoom);
}
-
TEST(GeoJSONOptions, FullConversion) {
- ValueMap map {
- // GeoJSON-VT
- {"maxzoom", 1.0f},
- {"buffer", 2.0f},
- {"tolerance", 3.0f},
-
- // Supercluster
- {"cluster", true},
- {"clusterRadius", 4.0f},
- {"clusterMaxZoom", 5.0f}
- };
- Value raw(map);
Error error;
- GeoJSONOptions converted = *convert<GeoJSONOptions>(raw, error);
+ GeoJSONOptions converted = *convertJSON<GeoJSONOptions>(R"JSON({
+ "maxzoom": 1,
+ "buffer": 2,
+ "tolerance": 3,
+ "cluster": true,
+ "clusterRadius": 4,
+ "clusterMaxZoom": 5
+ })JSON", error);
// GeoJSON-VT
ASSERT_EQ(converted.minzoom, 0);
diff --git a/test/style/conversion/layer.test.cpp b/test/style/conversion/layer.test.cpp
index d51d7d33e2..33cd329999 100644
--- a/test/style/conversion/layer.test.cpp
+++ b/test/style/conversion/layer.test.cpp
@@ -1,10 +1,8 @@
#include <mbgl/test/util.hpp>
-#include <mbgl/style/conversion.hpp>
-#include <mbgl/style/rapidjson_conversion.hpp>
+#include <mbgl/style/conversion/json.hpp>
#include <mbgl/style/conversion/layer.hpp>
#include <mbgl/style/layers/background_layer_impl.hpp>
-#include <mbgl/util/rapidjson.hpp>
using namespace mbgl;
using namespace mbgl::style;
@@ -12,10 +10,8 @@ using namespace mbgl::style::conversion;
using namespace std::literals::chrono_literals;
std::unique_ptr<Layer> parseLayer(const std::string& src) {
- JSDocument doc;
- doc.Parse<0>(src);
Error error;
- return std::move(*convert<std::unique_ptr<Layer>, JSValue>(doc, error));
+ return std::move(*convertJSON<std::unique_ptr<Layer>>(src, error));
}
TEST(StyleConversion, LayerTransition) {
diff --git a/test/style/conversion/light.test.cpp b/test/style/conversion/light.test.cpp
index 28e22b3550..67e48c942e 100644
--- a/test/style/conversion/light.test.cpp
+++ b/test/style/conversion/light.test.cpp
@@ -1,11 +1,10 @@
#include <mbgl/test/util.hpp>
#include <mbgl/style/conversion.hpp>
-#include <mbgl/style/rapidjson_conversion.hpp>
+#include <mbgl/style/conversion/json.hpp>
#include <mbgl/style/conversion/constant.hpp>
#include <mbgl/style/conversion/light.hpp>
#include <mbgl/style/position.hpp>
-#include <mbgl/util/rapidjson.hpp>
#include <mbgl/util/color.hpp>
#include <mbgl/util/chrono.hpp>
@@ -19,9 +18,7 @@ TEST(StyleConversion, Light) {
Error error;
auto parseLight = [&](const std::string& src) {
- JSDocument doc;
- doc.Parse<0>(src);
- return convert<Light>(doc, error);
+ return convertJSON<Light>(src, error);
};
{
diff --git a/test/style/expression/expression.test.cpp b/test/style/expression/expression.test.cpp
new file mode 100644
index 0000000000..694569695c
--- /dev/null
+++ b/test/style/expression/expression.test.cpp
@@ -0,0 +1,91 @@
+#include <mbgl/test/util.hpp>
+#include <mbgl/util/io.hpp>
+#include <mbgl/style/conversion.hpp>
+#include <mbgl/util/rapidjson.hpp>
+#include <mbgl/style/rapidjson_conversion.hpp>
+#include <mbgl/style/expression/is_expression.hpp>
+
+#include <rapidjson/document.h>
+
+#include <iostream>
+#include <fstream>
+#include <dirent.h>
+
+
+using namespace mbgl;
+using namespace mbgl::style;
+
+TEST(Expression, IsExpression) {
+ rapidjson::GenericDocument<rapidjson::UTF8<>, rapidjson::CrtAllocator> spec;
+ spec.Parse<0>(util::read_file("mapbox-gl-js/src/style-spec/reference/v8.json").c_str());
+ ASSERT_FALSE(spec.HasParseError());
+ ASSERT_TRUE(spec.IsObject() &&
+ spec.HasMember("expression_name") &&
+ spec["expression_name"].IsObject() &&
+ spec["expression_name"].HasMember("values") &&
+ spec["expression_name"]["values"].IsObject());
+
+ const auto& allExpressions = spec["expression_name"]["values"];
+
+ for(auto& entry : allExpressions.GetObject()) {
+ const std::string name { entry.name.GetString(), entry.name.GetStringLength() };
+ JSDocument document;
+ document.Parse<0>(R"([")" + name + R"("])");
+
+ const JSValue* expression = &document;
+ EXPECT_TRUE(expression::isExpression(conversion::Convertible(expression))) << name;
+ }
+}
+
+class ExpressionEqualityTest : public ::testing::TestWithParam<std::string> {};
+
+TEST_P(ExpressionEqualityTest, ExpressionEquality) {
+ const std::string base = std::string("test/fixtures/expression_equality/") + GetParam();
+
+ std::string error;
+ auto parse = [&](std::string filename, std::string& error_) -> std::unique_ptr<expression::Expression> {
+ rapidjson::GenericDocument<rapidjson::UTF8<>, rapidjson::CrtAllocator> document;
+ document.Parse<0>(util::read_file(filename).c_str());
+ assert(!document.HasParseError());
+ const JSValue* expression = &document;
+ expression::ParsingContext ctx;
+ expression::ParseResult parsed = ctx.parse(conversion::Convertible(expression));
+ if (!parsed) {
+ error_ = ctx.getErrors().size() > 0 ? ctx.getErrors()[0].message : "failed to parse";
+ };
+ return std::move(*parsed);
+ };
+
+ std::unique_ptr<expression::Expression> expression_a1 = parse(base + ".a.json", error);
+ ASSERT_TRUE(expression_a1) << GetParam() << ": " << error;
+
+ std::unique_ptr<expression::Expression> expression_a2 = parse(base + ".a.json", error);
+ ASSERT_TRUE(expression_a2) << GetParam() << ": " << error;
+
+ std::unique_ptr<expression::Expression> expression_b = parse(base + ".b.json", error);
+ ASSERT_TRUE(expression_b) << GetParam() << ": " << error;
+
+
+ EXPECT_TRUE(*expression_a1 == *expression_a2);
+ EXPECT_TRUE(*expression_a1 != *expression_b);
+}
+
+INSTANTIATE_TEST_CASE_P(Expression, ExpressionEqualityTest, ::testing::ValuesIn([] {
+ std::vector<std::string> names;
+ const std::string ending = ".a.json";
+
+ const std::string style_directory = "test/fixtures/expression_equality";
+ DIR *dir = opendir(style_directory.c_str());
+ if (dir != nullptr) {
+ for (dirent *dp = nullptr; (dp = readdir(dir)) != nullptr;) {
+ const std::string name = dp->d_name;
+ if (name.length() >= ending.length() && name.compare(name.length() - ending.length(), ending.length(), ending) == 0) {
+ names.push_back(name.substr(0, name.length() - ending.length()));
+ }
+ }
+ closedir(dir);
+ }
+
+ EXPECT_GT(names.size(), 0u);
+ return names;
+}()));
diff --git a/test/style/expression/util.test.cpp b/test/style/expression/util.test.cpp
new file mode 100644
index 0000000000..0337cd871f
--- /dev/null
+++ b/test/style/expression/util.test.cpp
@@ -0,0 +1,23 @@
+#include <mbgl/test/util.hpp>
+#include <mbgl/style/expression/util.hpp>
+
+using namespace mbgl;
+using namespace mbgl::style::expression;
+
+TEST(Expression, Util_rgba) {
+ Result<Color> valid = rgba(0, 0, 0, 0);
+ ASSERT_TRUE(valid);
+ ASSERT_EQ(valid->r, 0);
+ ASSERT_EQ(valid->g, 0);
+ ASSERT_EQ(valid->b, 0);
+ ASSERT_EQ(valid->a, 0);
+
+ ASSERT_FALSE(rgba(0, 0, 0, -0.1));
+ ASSERT_FALSE(rgba(0, 0, 0, 1.1));
+ ASSERT_FALSE(rgba(0, 0, -1, 1));
+ ASSERT_FALSE(rgba(0, 0, 256, 1));
+ ASSERT_FALSE(rgba(0, -1, 0, 1));
+ ASSERT_FALSE(rgba(0, 256, 0, 1));
+ ASSERT_FALSE(rgba(-1, 1, 0, 1));
+ ASSERT_FALSE(rgba(-256, 1, 0, 1));
+}
diff --git a/test/style/filter.test.cpp b/test/style/filter.test.cpp
index 96de125945..73f8e7626d 100644
--- a/test/style/filter.test.cpp
+++ b/test/style/filter.test.cpp
@@ -4,20 +4,15 @@
#include <mbgl/style/filter.hpp>
#include <mbgl/style/filter_evaluator.hpp>
-#include <mbgl/style/rapidjson_conversion.hpp>
-#include <mbgl/style/conversion.hpp>
+#include <mbgl/style/conversion/json.hpp>
#include <mbgl/style/conversion/filter.hpp>
-#include <rapidjson/document.h>
-
using namespace mbgl;
using namespace mbgl::style;
Filter parse(const char * expression) {
- rapidjson::GenericDocument<rapidjson::UTF8<>, rapidjson::CrtAllocator> doc;
- doc.Parse<0>(expression);
conversion::Error error;
- optional<Filter> filter = conversion::convert<Filter, JSValue>(doc, error);
+ optional<Filter> filter = conversion::convertJSON<Filter>(expression, error);
EXPECT_TRUE(bool(filter));
return *filter;
}
diff --git a/test/style/source.test.cpp b/test/style/source.test.cpp
index 919260ffe9..bee1b867b8 100644
--- a/test/style/source.test.cpp
+++ b/test/style/source.test.cpp
@@ -38,6 +38,7 @@
#include <cstdint>
using namespace mbgl;
+using SourceType = mbgl::style::SourceType;
class SourceTest {
public:
diff --git a/test/text/glyph_loader.test.cpp b/test/text/glyph_loader.test.cpp
index be197ebb46..20ac045925 100644
--- a/test/text/glyph_loader.test.cpp
+++ b/test/text/glyph_loader.test.cpp
@@ -214,3 +214,52 @@ TEST(GlyphManager, LoadingInvalid) {
{{{"Test Stack"}}, {u'A', u'E'}}
});
}
+
+TEST(GlyphManager, ImmediateFileSource) {
+ class GlyphManagerTestSynchronous {
+ public:
+ util::RunLoop loop;
+ StubFileSource fileSource = { StubFileSource::ResponseType::Synchronous };
+ StubGlyphManagerObserver observer;
+ StubGlyphRequestor requestor;
+ GlyphManager glyphManager { fileSource };
+
+ void run(const std::string& url, GlyphDependencies dependencies) {
+ // Squelch logging.
+ Log::setObserver(std::make_unique<Log::NullObserver>());
+
+ glyphManager.setURL(url);
+ glyphManager.setObserver(&observer);
+ glyphManager.getGlyphs(requestor, std::move(dependencies));
+
+ loop.run();
+ }
+
+ void end() {
+ loop.stop();
+ }
+ };
+
+ GlyphManagerTestSynchronous test;
+
+ test.fileSource.glyphsResponse = [&] (const Resource&) {
+ Response response;
+ response.data = std::make_shared<std::string>(util::read_file("test/fixtures/resources/glyphs.pbf"));
+ return response;
+ };
+
+ test.observer.glyphsError = [&] (const FontStack&, const GlyphRange&, std::exception_ptr) {
+ FAIL();
+ test.end();
+ };
+
+ test.requestor.glyphsAvailable = [&] (GlyphMap) {
+ test.end();
+ };
+
+ test.run(
+ "test/fixtures/resources/glyphs.pbf",
+ GlyphDependencies {
+ {{{"Test Stack"}}, {u'a', u'å', u' '}}
+ });
+}
diff --git a/test/util/geo.test.cpp b/test/util/geo.test.cpp
index d0d01b6f88..38f29d1dd4 100644
--- a/test/util/geo.test.cpp
+++ b/test/util/geo.test.cpp
@@ -220,3 +220,9 @@ TEST(LatLngBounds, FromTileID) {
ASSERT_DOUBLE_EQ(util::LATITUDE_MAX, bounds.north());
}
}
+
+TEST(LatLngBounds, Contains) {
+ const LatLngBounds bounds( CanonicalTileID(4,2,6));
+ const LatLngBounds innerBounds( CanonicalTileID(9,82,197));
+ EXPECT_TRUE(bounds.contains(innerBounds));
+}
diff --git a/test/util/mapbox.test.cpp b/test/util/mapbox.test.cpp
index cdbd85118f..301475dae4 100644
--- a/test/util/mapbox.test.cpp
+++ b/test/util/mapbox.test.cpp
@@ -7,6 +7,7 @@
#include <stdexcept>
using namespace mbgl;
+using SourceType = mbgl::style::SourceType;
TEST(Mapbox, SourceURL) {
EXPECT_EQ(