From 4e6c4987d16c3e5d820623f33b9a6bf9d2c91aa8 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 26 Sep 2017 14:14:44 -0700 Subject: [PATCH] Replace compile-time polymorphism with runtime polymorphism in the conversion system --- cmake/benchmark.cmake | 1 + cmake/core-files.cmake | 14 +- cmake/node.cmake | 1 + cmake/test-files.cmake | 1 - include/mbgl/style/conversion.hpp | 254 +++++++++++++++++- include/mbgl/style/conversion/constant.hpp | 98 +------ include/mbgl/style/conversion/coordinate.hpp | 21 +- .../conversion/data_driven_property_value.hpp | 3 +- include/mbgl/style/conversion/filter.hpp | 243 +---------------- include/mbgl/style/conversion/function.hpp | 47 ++-- include/mbgl/style/conversion/geojson.hpp | 10 +- .../mbgl/style/conversion/geojson_options.hpp | 79 +----- include/mbgl/style/conversion/layer.hpp | 210 +-------------- include/mbgl/style/conversion/light.hpp | 106 +------- .../conversion/make_property_setters.hpp | 211 --------------- .../conversion/make_property_setters.hpp.ejs | 48 ---- include/mbgl/style/conversion/position.hpp | 14 +- .../mbgl/style/conversion/property_value.hpp | 3 +- include/mbgl/style/conversion/source.hpp | 176 +----------- include/mbgl/style/conversion/tileset.hpp | 65 +---- .../style/conversion/transition_options.hpp | 32 +-- include/mbgl/util/tileset.hpp | 1 + platform/android/config.cmake | 1 - .../android/src/style/android_conversion.hpp | 147 +++++----- .../android/src/style/conversion/filter.hpp | 3 +- .../android/src/style/conversion/geojson.hpp | 24 -- .../src/style/conversion/url_or_tileset.hpp | 9 +- platform/android/src/style/layers/layer.cpp | 21 +- .../src/style/sources/geojson_source.cpp | 10 +- platform/android/src/style/value.cpp | 2 - platform/android/src/style/value.hpp | 8 +- platform/darwin/src/MGLConversion.h | 223 +++++++-------- platform/darwin/src/MGLStyleValue_Private.h | 4 +- platform/node/src/node_conversion.hpp | 183 +++++++------ platform/node/src/node_geojson.hpp | 17 -- platform/node/src/node_map.cpp | 14 +- platform/qt/qt.cmake | 3 + platform/qt/src/qmapboxgl.cpp | 10 + platform/qt/src/qt_conversion.hpp | 191 +++++++------ platform/qt/src/qt_geojson.cpp | 166 ++++++++++++ platform/qt/src/qt_geojson.hpp | 194 +------------ scripts/generate-style-code.js | 4 +- src/mbgl/style/conversion/constant.cpp | 94 +++++++ src/mbgl/style/conversion/coordinate.cpp | 29 ++ src/mbgl/style/conversion/filter.cpp | 248 +++++++++++++++++ src/mbgl/style/conversion/geojson.cpp | 18 +- src/mbgl/style/conversion/geojson_options.cpp | 85 ++++++ src/mbgl/style/conversion/json.hpp | 2 +- src/mbgl/style/conversion/layer.cpp | 206 ++++++++++++++ src/mbgl/style/conversion/light.cpp | 115 ++++++++ .../conversion/make_property_setters.hpp | 209 ++++++++++++++ .../conversion/make_property_setters.hpp.ejs | 46 ++++ src/mbgl/style/conversion/position.cpp | 22 ++ .../mbgl/style/conversion/property_setter.hpp | 14 +- src/mbgl/style/conversion/source.cpp | 175 ++++++++++++ src/mbgl/style/conversion/tileset.cpp | 73 +++++ .../style/conversion/transition_options.cpp | 40 +++ src/mbgl/style/parser.cpp | 6 +- src/mbgl/style/rapidjson_conversion.hpp | 152 ++++++----- test/src/mbgl/test/conversion_stubs.hpp | 124 --------- 60 files changed, 2395 insertions(+), 2135 deletions(-) delete mode 100644 include/mbgl/style/conversion/make_property_setters.hpp delete mode 100644 include/mbgl/style/conversion/make_property_setters.hpp.ejs delete mode 100644 platform/android/src/style/conversion/geojson.hpp delete mode 100644 platform/node/src/node_geojson.hpp create mode 100644 platform/qt/src/qt_geojson.cpp create mode 100644 src/mbgl/style/conversion/constant.cpp create mode 100644 src/mbgl/style/conversion/coordinate.cpp create mode 100644 src/mbgl/style/conversion/filter.cpp create mode 100644 src/mbgl/style/conversion/geojson_options.cpp create mode 100644 src/mbgl/style/conversion/layer.cpp create mode 100644 src/mbgl/style/conversion/light.cpp create mode 100644 src/mbgl/style/conversion/make_property_setters.hpp create mode 100644 src/mbgl/style/conversion/make_property_setters.hpp.ejs create mode 100644 src/mbgl/style/conversion/position.cpp rename {include => src}/mbgl/style/conversion/property_setter.hpp (76%) create mode 100644 src/mbgl/style/conversion/source.cpp create mode 100644 src/mbgl/style/conversion/tileset.cpp create mode 100644 src/mbgl/style/conversion/transition_options.cpp delete mode 100644 test/src/mbgl/test/conversion_stubs.hpp diff --git a/cmake/benchmark.cmake b/cmake/benchmark.cmake index 87351e97b1d..623483bedf9 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 54b4079cffc..7621283e7ee 100644 --- a/cmake/core-files.cmake +++ b/cmake/core-files.cmake @@ -370,16 +370,26 @@ set(MBGL_CORE_FILES include/mbgl/style/conversion/geojson_options.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/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/function include/mbgl/style/function/camera_function.hpp diff --git a/cmake/node.cmake b/cmake/node.cmake index c256b13f936..388a98b68f3 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 diff --git a/cmake/test-files.cmake b/cmake/test-files.cmake index 027c34f31ed..1aee6223b3d 100644 --- a/cmake/test-files.cmake +++ b/cmake/test-files.cmake @@ -51,7 +51,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 diff --git a/include/mbgl/style/conversion.hpp b/include/mbgl/style/conversion.hpp index 27504a89b17..0b7e0b2b2f2 100644 --- a/include/mbgl/style/conversion.hpp +++ b/include/mbgl/style/conversion.hpp @@ -1,6 +1,8 @@ #pragma once #include +#include +#include #include @@ -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` * `std::unique_ptr` @@ -20,15 +21,31 @@ namespace conversion { A single template function serves as the public interface: - template - optional convert(const V& value, Error& error); + template + optional 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` 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`, absence indicating `v` is not a JSON number * `toDouble(v)` -- returns `optional`, absence indicating `v` is not a JSON number * `toString(v)` -- returns `optional`, absence indicating `v` is not a JSON string - * `toValue(v)` -- returns `optional`, a variant type, for generic conversion, + * `toValue(v)` -- returns `optional`, 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 +class ConversionTraits; + +class Convertible { +public: + template + Convertible(T&& value) : vtable(vtableForType>()) { + static_assert(sizeof(Storage) >= sizeof(std::decay_t), "Storage must be large enough to hold value type"); + new (static_cast(&storage)) std::decay_t(std::forward(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 objectMember(const Convertible& v, const char * name) { + assert(v.vtable); + return v.vtable->objectMember(v.storage, name); + } + + friend inline optional eachMember(const Convertible& v, const std::function (const std::string&, const Convertible&)>& fn) { + assert(v.vtable); + return v.vtable->eachMember(v.storage, fn); + } + + friend inline optional toBool(const Convertible& v) { + assert(v.vtable); + return v.vtable->toBool(v.storage); + } + + friend inline optional toNumber(const Convertible& v) { + assert(v.vtable); + return v.vtable->toNumber(v.storage); + } + + friend inline optional toDouble(const Convertible& v) { + assert(v.vtable); + return v.vtable->toDouble(v.storage); + } + + friend inline optional toString(const Convertible& v) { + assert(v.vtable); + return v.vtable->toString(v.storage); + } + + friend inline optional toValue(const Convertible& v) { + assert(v.vtable); + return v.vtable->toValue(v.storage); + } + + friend inline optional 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 + // 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 (*objectMember) (const Storage&, const char *); + optional (*eachMember) (const Storage&, const std::function (const std::string&, const Convertible&)>&); + + optional (*toBool) (const Storage&); + optional (*toNumber) (const Storage&); + optional (*toDouble) (const Storage&); + optional (*toString) (const Storage&); + optional (*toValue) (const Storage&); + + // https://github.com/mapbox/mapbox-gl-native/issues/5623 + optional (*toGeoJSON) (const Storage&, Error&); + }; + + template + static VTable* vtableForType() { + using Traits = ConversionTraits; + static VTable vtable = { + [] (Storage&& src, Storage& dest) { + auto srcValue = reinterpret_cast(src); + new (static_cast(&dest)) T(std::move(srcValue)); + srcValue.~T(); + }, + [] (Storage& s) { + reinterpret_cast(s).~T(); + }, + [] (const Storage& s) { + return Traits::isUndefined(reinterpret_cast(s)); + }, + [] (const Storage& s) { + return Traits::isArray(reinterpret_cast(s)); + }, + [] (const Storage& s) { + return Traits::arrayLength(reinterpret_cast(s)); + }, + [] (const Storage& s, std::size_t i) { + return Convertible(Traits::arrayMember(reinterpret_cast(s), i)); + }, + [] (const Storage& s) { + return Traits::isObject(reinterpret_cast(s)); + }, + [] (const Storage& s, const char * key) { + optional member = Traits::objectMember(reinterpret_cast(s), key); + if (member) { + return optional(Convertible(std::move(*member))); + } else { + return optional(); + } + }, + [] (const Storage& s, const std::function (const std::string&, const Convertible&)>& fn) { + return Traits::eachMember(reinterpret_cast(s), [&](const std::string& k, T&& v) { + return fn(k, Convertible(std::move(v))); + }); + }, + [] (const Storage& s) { + return Traits::toBool(reinterpret_cast(s)); + }, + [] (const Storage& s) { + return Traits::toNumber(reinterpret_cast(s)); + }, + [] (const Storage& s) { + return Traits::toDouble(reinterpret_cast(s)); + }, + [] (const Storage& s) { + return Traits::toString(reinterpret_cast(s)); + }, + [] (const Storage& s) { + return Traits::toValue(reinterpret_cast(s)); + }, + [] (const Storage& s, Error& err) { + return Traits::toGeoJSON(reinterpret_cast(s), err); + } + }; + return &vtable; + } + + VTable* vtable; + Storage storage; +}; + template struct Converter; -template -optional convert(const V& value, Error& error, Args&&...args) { +template +optional convert(const Convertible& value, Error& error, Args&&...args) { return Converter()(value, error, std::forward(args)...); } diff --git a/include/mbgl/style/conversion/constant.hpp b/include/mbgl/style/conversion/constant.hpp index 07c0a35fae4..7b3249da52b 100644 --- a/include/mbgl/style/conversion/constant.hpp +++ b/include/mbgl/style/conversion/constant.hpp @@ -1,7 +1,6 @@ #pragma once #include -#include #include #include #include @@ -16,47 +15,22 @@ namespace conversion { template <> struct Converter { - template - optional operator()(const V& value, Error& error) const { - optional converted = toBool(value); - if (!converted) { - error = { "value must be a boolean" }; - return {}; - } - return *converted; - } + optional operator()(const Convertible& value, Error& error) const; }; template <> struct Converter { - template - optional operator()(const V& value, Error& error) const { - optional converted = toNumber(value); - if (!converted) { - error = { "value must be a number" }; - return {}; - } - return *converted; - } + optional operator()(const Convertible& value, Error& error) const; }; template <> struct Converter { - template - optional operator()(const V& value, Error& error) const { - optional converted = toString(value); - if (!converted) { - error = { "value must be a string" }; - return {}; - } - return *converted; - } + optional operator()(const Convertible& value, Error& error) const; }; template struct Converter::value>> { - template - optional operator()(const V& value, Error& error) const { + optional operator()(const Convertible& value, Error& error) const { optional string = toString(value); if (!string) { error = { "value must be a string" }; @@ -75,28 +49,12 @@ struct Converter::value>> { template <> struct Converter { - template - optional operator()(const V& value, Error& error) const { - optional string = toString(value); - if (!string) { - error = { "value must be a string" }; - return {}; - } - - optional color = Color::parse(*string); - if (!color) { - error = { "value must be a valid color" }; - return {}; - } - - return *color; - } + optional operator()(const Convertible& value, Error& error) const; }; template struct Converter> { - template - optional> operator()(const V& value, Error& error) const { + optional> 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> { template <> struct Converter> { - template - optional> operator()(const V& value, Error& error) const { - if (!isArray(value)) { - error = { "value must be an array" }; - return {}; - } - - std::vector result; - result.reserve(arrayLength(value)); - - for (std::size_t i = 0; i < arrayLength(value); ++i) { - optional number = toNumber(arrayMember(value, i)); - if (!number) { - error = { "value must be an array of numbers" }; - return {}; - } - result.push_back(*number); - } - - return result; - } + optional> operator()(const Convertible& value, Error& error) const; }; template <> struct Converter> { - template - optional> operator()(const V& value, Error& error) const { - if (!isArray(value)) { - error = { "value must be an array" }; - return {}; - } - - std::vector result; - result.reserve(arrayLength(value)); - - for (std::size_t i = 0; i < arrayLength(value); ++i) { - optional string = toString(arrayMember(value, i)); - if (!string) { - error = { "value must be an array of strings" }; - return {}; - } - result.push_back(*string); - } - - return result; - } + optional> 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 732624e77f3..e11db5e32f3 100644 --- a/include/mbgl/style/conversion/coordinate.hpp +++ b/include/mbgl/style/conversion/coordinate.hpp @@ -10,26 +10,7 @@ namespace conversion { template<> struct Converter { public: - template - optional 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 latitude = toDouble(arrayMember(value, 1)); - optional 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 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 79b15dcfb0d..1e54c15a493 100644 --- a/include/mbgl/style/conversion/data_driven_property_value.hpp +++ b/include/mbgl/style/conversion/data_driven_property_value.hpp @@ -11,8 +11,7 @@ namespace conversion { template struct Converter> { - template - optional> operator()(const V& value, Error& error) const { + optional> operator()(const Convertible& value, Error& error) const { if (isUndefined(value)) { return DataDrivenPropertyValue(); } else if (!isObject(value)) { diff --git a/include/mbgl/style/conversion/filter.hpp b/include/mbgl/style/conversion/filter.hpp index 986d1bf80df..9daf6ea7a4e 100644 --- a/include/mbgl/style/conversion/filter.hpp +++ b/include/mbgl/style/conversion/filter.hpp @@ -2,7 +2,6 @@ #include #include -#include namespace mbgl { namespace style { @@ -11,247 +10,7 @@ namespace conversion { template <> struct Converter { public: - template - optional 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 op = toString(arrayMember(value, 0)); - if (!op) { - error = { "filter operator must be a string" }; - return {}; - } - - if (*op == "==") { - return convertEqualityFilter(value, error); - } else if (*op == "!=") { - return convertEqualityFilter(value, error); - } else if (*op == ">") { - return convertBinaryFilter(value, error); - } else if (*op == ">=") { - return convertBinaryFilter(value, error); - } else if (*op == "<") { - return convertBinaryFilter(value, error); - } else if (*op == "<=") { - return convertBinaryFilter(value, error); - } else if (*op == "in") { - return convertSetFilter(value, error); - } else if (*op == "!in") { - return convertSetFilter(value, error); - } else if (*op == "all") { - return convertCompoundFilter(value, error); - } else if (*op == "any") { - return convertCompoundFilter(value, error); - } else if (*op == "none") { - return convertCompoundFilter(value, error); - } else if (*op == "has") { - return convertUnaryFilter(value, error); - } else if (*op == "!has") { - return convertUnaryFilter(value, error); - } - - error = { R"(filter operator must be one of "==", "!=", ">", ">=", "<", "<=", "in", "!in", "all", "any", "none", "has", or "!has")" }; - return {}; - } - -private: - optional normalizeValue(const optional& value, Error& error) const { - if (!value) { - error = { "filter expression value must be a boolean, number, or string" }; - return {}; - } else { - return *value; - } - } - - template - optional toFeatureType(const V& value, Error& error) const { - optional 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 - optional toFeatureIdentifier(const V& value, Error& error) const { - optional 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 { return { t }; }, - [] ( int64_t t) -> optional { return { t }; }, - [] ( double t) -> optional { return { t }; }, - [] (const std::string& t) -> optional { return { t }; }, - [&] (const auto&) -> optional { - error = { "filter expression value must be a boolean, number, or string" }; - return {}; - }); - } - } - - template - optional convertUnaryFilter(const V& value, Error& error) const { - if (arrayLength(value) < 2) { - error = { "filter expression must have 2 elements" }; - return {}; - } - - optional 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 - optional convertEqualityFilter(const V& value, Error& error) const { - if (arrayLength(value) < 3) { - error = { "filter expression must have 3 elements" }; - return {}; - } - - optional key = toString(arrayMember(value, 1)); - if (!key) { - error = { "filter expression key must be a string" }; - return {}; - } - - if (*key == "$type") { - optional filterValue = toFeatureType(arrayMember(value, 2), error); - if (!filterValue) { - return {}; - } - - return { TypeFilterType { *filterValue } }; - - } else if (*key == "$id") { - optional filterValue = toFeatureIdentifier(arrayMember(value, 2), error); - if (!filterValue) { - return {}; - } - - return { IdentifierFilterType { *filterValue } }; - - } else { - optional filterValue = normalizeValue(toValue(arrayMember(value, 2)), error); - if (!filterValue) { - return {}; - } - - return { FilterType { *key, *filterValue } }; - } - } - - template - optional convertBinaryFilter(const V& value, Error& error) const { - if (arrayLength(value) < 3) { - error = { "filter expression must have 3 elements" }; - return {}; - } - - optional key = toString(arrayMember(value, 1)); - if (!key) { - error = { "filter expression key must be a string" }; - return {}; - } - - optional filterValue = normalizeValue(toValue(arrayMember(value, 2)), error); - if (!filterValue) { - return {}; - } - - return { FilterType { *key, *filterValue } }; - } - - template - optional convertSetFilter(const V& value, Error& error) const { - if (arrayLength(value) < 2) { - error = { "filter expression must at least 2 elements" }; - return {}; - } - - optional key = toString(arrayMember(value, 1)); - if (!key) { - error = { "filter expression key must be a string" }; - return {}; - } - - if (*key == "$type") { - std::vector values; - for (std::size_t i = 2; i < arrayLength(value); ++i) { - optional filterValue = toFeatureType(arrayMember(value, i), error); - if (!filterValue) { - return {}; - } - values.push_back(*filterValue); - } - - return { TypeFilterType { std::move(values) } }; - - } else if (*key == "$id") { - std::vector values; - for (std::size_t i = 2; i < arrayLength(value); ++i) { - optional filterValue = toFeatureIdentifier(arrayMember(value, i), error); - if (!filterValue) { - return {}; - } - values.push_back(*filterValue); - } - - return { IdentifierFilterType { std::move(values) } }; - - } else { - std::vector values; - for (std::size_t i = 2; i < arrayLength(value); ++i) { - optional filterValue = normalizeValue(toValue(arrayMember(value, i)), error); - if (!filterValue) { - return {}; - } - values.push_back(*filterValue); - } - - return { FilterType { *key, std::move(values) } }; - } - } - - template - optional convertCompoundFilter(const V& value, Error& error) const { - std::vector filters; - for (std::size_t i = 1; i < arrayLength(value); ++i) { - optional element = operator()(arrayMember(value, i), error); - if (!element) { - return {}; - } - filters.push_back(*element); - } - - return { FilterType { std::move(filters) } }; - } + optional 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 752b6dd0451..e2308849442 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 -optional> convertStops(const V& value, Error& error) { +template +optional> convertStops(const Convertible& value, Error& error) { auto stopsValue = objectMember(value, "stops"); if (!stopsValue) { error = { "function value must specify stops" }; @@ -63,8 +63,7 @@ template struct Converter> { static constexpr const char * type = "exponential"; - template - optional> operator()(const V& value, Error& error) const { + optional> operator()(const Convertible& value, Error& error) const { auto stops = convertStops(value, error); if (!stops) { return {}; @@ -89,8 +88,7 @@ template struct Converter> { static constexpr const char * type = "interval"; - template - optional> operator()(const V& value, Error& error) const { + optional> operator()(const Convertible& value, Error& error) const { auto stops = convertStops(value, error); if (!stops) { return {}; @@ -101,8 +99,7 @@ struct Converter> { template <> struct Converter { - template - optional operator()(const V& value, Error& error) const { + optional operator()(const Convertible& value, Error& error) const { auto b = toBool(value); if (b) { return { *b }; @@ -127,8 +124,7 @@ template struct Converter> { static constexpr const char * type = "categorical"; - template - optional> operator()(const V& value, Error& error) const { + optional> operator()(const Convertible& value, Error& error) const { auto stops = convertStops(value, error); if (!stops) { return {}; @@ -142,8 +138,7 @@ template struct Converter> { static constexpr const char * type = "identity"; - template - optional> operator()(const V&, Error&) const { + optional> operator()(const Convertible&, Error&) const { return IdentityStops(); } }; @@ -154,8 +149,7 @@ struct StopsConverter; template struct StopsConverter> { public: - template - optional> operator()(const V& value, Error& error) const { + optional> operator()(const Convertible& value, Error& error) const { std::string type = util::Interpolatable::value ? "exponential" : "interval"; auto typeValue = objectMember(value, "type"); @@ -193,8 +187,7 @@ struct StopsConverter> { template struct Converter> { - template - optional> operator()(const V& value, Error& error) const { + optional> operator()(const Convertible& value, Error& error) const { if (!isObject(value)) { error = { "function must be an object" }; return {}; @@ -209,8 +202,8 @@ struct Converter> { } }; -template -optional> convertDefaultValue(const V& value, Error& error) { +template +optional> convertDefaultValue(const Convertible& value, Error& error) { auto defaultValueValue = objectMember(value, "default"); if (!defaultValueValue) { return optional(); @@ -227,8 +220,7 @@ optional> convertDefaultValue(const V& value, Error& error) { template struct Converter> { - template - optional> operator()(const V& value, Error& error) const { + optional> 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 { template struct Converter> { - template - optional> operator()(const V& value, Error& error) const { + optional> operator()(const Convertible& value, Error& error) const { if (!isObject(value)) { error = { "stop must be an object" }; return {}; @@ -304,8 +295,7 @@ template struct Converter> { static constexpr const char * type = "exponential"; - template - optional> operator()(const V& value, Error& error) const { + optional> operator()(const Convertible& value, Error& error) const { auto stops = convertStops, T>(value, error); if (!stops) { return {}; @@ -330,8 +320,7 @@ template struct Converter> { static constexpr const char * type = "interval"; - template - optional> operator()(const V& value, Error& error) const { + optional> operator()(const Convertible& value, Error& error) const { auto stops = convertStops, T>(value, error); if (!stops) { return {}; @@ -350,8 +339,7 @@ template struct Converter> { static constexpr const char * type = "categorical"; - template - optional> operator()(const V& value, Error& error) const { + optional> operator()(const Convertible& value, Error& error) const { auto stops = convertStops, T>(value, error); if (!stops) { return {}; @@ -368,8 +356,7 @@ struct Converter> { template struct Converter> { - template - optional> operator()(const V& value, Error& error) const { + optional> 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 0b594f066c7..403c5f953b2 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 parseGeoJSON(const std::string&, Error&); + template <> struct Converter { public: - optional 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 - optional operator()(const V&, Error&) const; + optional 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 1c9c18250c5..3f625babb6f 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 { - - template - optional operator()(const V& value, Error& error) const { - GeoJSONOptions options; - - const auto minzoomValue = objectMember(value, "minzoom"); - if (minzoomValue) { - if (toNumber(*minzoomValue)) { - options.minzoom = static_cast(*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(*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(*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(*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(*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(*toNumber(*clusterRadiusValue)); - } else { - error = { "GeoJSON source clusterRadius value must be a number" }; - return {}; - } - } - - return { options }; - } - + optional operator()(const Convertible& value, Error& error) const; }; } // namespace conversion diff --git a/include/mbgl/style/conversion/layer.hpp b/include/mbgl/style/conversion/layer.hpp index 1fe467165d3..1c0e2e2f07d 100644 --- a/include/mbgl/style/conversion/layer.hpp +++ b/include/mbgl/style/conversion/layer.hpp @@ -1,220 +1,24 @@ #pragma once #include -#include -#include -#include -#include -#include -#include -#include #include -#include -#include -#include + +#include namespace mbgl { namespace style { namespace conversion { -template -optional setLayoutProperty(Layer& layer, const std::string& name, const V& 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); -} - -template -optional setPaintProperty(Layer& layer, const std::string& name, const V& 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); -} - -template -optional 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> { public: - template - optional> 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 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 type = toString(*typeValue); - if (!type) { - error = { "layer type must be a string" }; - return {}; - } - - optional> converted; - - if (*type == "fill") { - converted = convertVectorLayer(*id, value, error); - } else if (*type == "fill-extrusion") { - converted = convertVectorLayer(*id, value, error); - } else if (*type == "line") { - converted = convertVectorLayer(*id, value, error); - } else if (*type == "circle") { - converted = convertVectorLayer(*id, value, error); - } else if (*type == "symbol") { - converted = convertVectorLayer(*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 = std::move(*converted); - - auto minzoomValue = objectMember(value, "minzoom"); - if (minzoomValue) { - optional minzoom = toNumber(*minzoomValue); - if (!minzoom) { - error = { "minzoom must be numeric" }; - return {}; - } - layer->setMinZoom(*minzoom); - } - - auto maxzoomValue = objectMember(value, "maxzoom"); - if (maxzoomValue) { - optional 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_ = eachMember(*layoutValue, [&] (const std::string& k, const V& v) { - return setLayoutProperty(*layer, k, v); - }); - if (error_) { - error = *error_; - return {}; - } - } - - optional error_ = setPaintProperties(*layer, value); - if (error_) { - error = *error_; - return {}; - } - - return std::move(layer); - } - -private: - template - optional> 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 source = toString(*sourceValue); - if (!source) { - error = { "layer source must be a string" }; - return {}; - } - - std::unique_ptr layer = std::make_unique(id, *source); - - auto sourceLayerValue = objectMember(value, "source-layer"); - if (sourceLayerValue) { - optional 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 = convert(*filterValue, error); - if (!filter) { - return {}; - } - layer->setFilter(*filter); - } - - return { std::move(layer) }; - } - - template - optional> 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 source = toString(*sourceValue); - if (!source) { - error = { "layer source must be a string" }; - return {}; - } - - return { std::make_unique(id, *source) }; - } - - template - optional> convertBackgroundLayer(const std::string& id, const V&, Error&) const { - return { std::make_unique(id) }; - } + optional> operator()(const Convertible& value, Error& error) const; }; +optional setLayoutProperty(Layer& layer, const std::string& name, const Convertible& value); +optional setPaintProperty(Layer& layer, const std::string& name, const Convertible& value); +optional 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 ba162516c0e..289fca2e312 100644 --- a/include/mbgl/style/conversion/light.hpp +++ b/include/mbgl/style/conversion/light.hpp @@ -2,9 +2,6 @@ #include #include -#include -#include -#include namespace mbgl { namespace style { @@ -13,108 +10,7 @@ namespace conversion { template <> struct Converter { public: - template - optional 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> convertedAnchor = - convert>(*anchor, error); - - if (convertedAnchor) { - light.setAnchor(*convertedAnchor); - } else { - return {}; - } - } - - const auto anchorTransition = objectMember(value, "anchor-transition"); - if (anchorTransition) { - optional transition = - convert(*anchorTransition, error); - if (transition) { - light.setAnchorTransition(*transition); - } else { - return {}; - } - } - - const auto color = objectMember(value, "color"); - if (color) { - optional> convertedColor = - convert>(*color, error); - - if (convertedColor) { - light.setColor(*convertedColor); - } else { - return {}; - } - } - - const auto colorTransition = objectMember(value, "color-transition"); - if (colorTransition) { - optional transition = - convert(*colorTransition, error); - if (transition) { - light.setColorTransition(*transition); - } else { - return {}; - } - } - - const auto position = objectMember(value, "position"); - if (position) { - optional> convertedPosition = - convert>(*position, error); - - if (convertedPosition) { - light.setPosition(*convertedPosition); - } else { - return {}; - } - } - - const auto positionTransition = objectMember(value, "position-transition"); - if (positionTransition) { - optional transition = - convert(*positionTransition, error); - if (transition) { - light.setPositionTransition(*transition); - } else { - return {}; - } - } - - const auto intensity = objectMember(value, "intensity"); - if (intensity) { - optional> convertedIntensity = - convert>(*intensity, error); - - if (convertedIntensity) { - light.setIntensity(*convertedIntensity); - } else { - return {}; - } - } - - const auto intensityTransition = objectMember(value, "intensity-transition"); - if (intensityTransition) { - optional transition = - convert(*intensityTransition, error); - if (transition) { - light.setIntensityTransition(*transition); - } else { - return {}; - } - } - return { std::move(light) }; - }; + optional 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 59b0e7be320..00000000000 --- 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 - -#include -#include -#include -#include -#include -#include -#include - -#include - -namespace mbgl { -namespace style { -namespace conversion { - -template -auto makeLayoutPropertySetters() { - std::unordered_map> result; - - result["visibility"] = &setVisibility; - - - result["line-cap"] = &setProperty, &LineLayer::setLineCap>; - result["line-join"] = &setProperty, &LineLayer::setLineJoin>; - result["line-miter-limit"] = &setProperty, &LineLayer::setLineMiterLimit>; - result["line-round-limit"] = &setProperty, &LineLayer::setLineRoundLimit>; - - result["symbol-placement"] = &setProperty, &SymbolLayer::setSymbolPlacement>; - result["symbol-spacing"] = &setProperty, &SymbolLayer::setSymbolSpacing>; - result["symbol-avoid-edges"] = &setProperty, &SymbolLayer::setSymbolAvoidEdges>; - result["icon-allow-overlap"] = &setProperty, &SymbolLayer::setIconAllowOverlap>; - result["icon-ignore-placement"] = &setProperty, &SymbolLayer::setIconIgnorePlacement>; - result["icon-optional"] = &setProperty, &SymbolLayer::setIconOptional>; - result["icon-rotation-alignment"] = &setProperty, &SymbolLayer::setIconRotationAlignment>; - result["icon-size"] = &setProperty, &SymbolLayer::setIconSize>; - result["icon-text-fit"] = &setProperty, &SymbolLayer::setIconTextFit>; - result["icon-text-fit-padding"] = &setProperty>, &SymbolLayer::setIconTextFitPadding>; - result["icon-image"] = &setProperty, &SymbolLayer::setIconImage>; - result["icon-rotate"] = &setProperty, &SymbolLayer::setIconRotate>; - result["icon-padding"] = &setProperty, &SymbolLayer::setIconPadding>; - result["icon-keep-upright"] = &setProperty, &SymbolLayer::setIconKeepUpright>; - result["icon-offset"] = &setProperty>, &SymbolLayer::setIconOffset>; - result["icon-anchor"] = &setProperty, &SymbolLayer::setIconAnchor>; - result["icon-pitch-alignment"] = &setProperty, &SymbolLayer::setIconPitchAlignment>; - result["text-pitch-alignment"] = &setProperty, &SymbolLayer::setTextPitchAlignment>; - result["text-rotation-alignment"] = &setProperty, &SymbolLayer::setTextRotationAlignment>; - result["text-field"] = &setProperty, &SymbolLayer::setTextField>; - result["text-font"] = &setProperty>, &SymbolLayer::setTextFont>; - result["text-size"] = &setProperty, &SymbolLayer::setTextSize>; - result["text-max-width"] = &setProperty, &SymbolLayer::setTextMaxWidth>; - result["text-line-height"] = &setProperty, &SymbolLayer::setTextLineHeight>; - result["text-letter-spacing"] = &setProperty, &SymbolLayer::setTextLetterSpacing>; - result["text-justify"] = &setProperty, &SymbolLayer::setTextJustify>; - result["text-anchor"] = &setProperty, &SymbolLayer::setTextAnchor>; - result["text-max-angle"] = &setProperty, &SymbolLayer::setTextMaxAngle>; - result["text-rotate"] = &setProperty, &SymbolLayer::setTextRotate>; - result["text-padding"] = &setProperty, &SymbolLayer::setTextPadding>; - result["text-keep-upright"] = &setProperty, &SymbolLayer::setTextKeepUpright>; - result["text-transform"] = &setProperty, &SymbolLayer::setTextTransform>; - result["text-offset"] = &setProperty>, &SymbolLayer::setTextOffset>; - result["text-allow-overlap"] = &setProperty, &SymbolLayer::setTextAllowOverlap>; - result["text-ignore-placement"] = &setProperty, &SymbolLayer::setTextIgnorePlacement>; - result["text-optional"] = &setProperty, &SymbolLayer::setTextOptional>; - - - - - - return result; -} - -template -auto makePaintPropertySetters() { - std::unordered_map> result; - - result["fill-antialias"] = &setProperty, &FillLayer::setFillAntialias>; - result["fill-antialias-transition"] = &setTransition; - result["fill-opacity"] = &setProperty, &FillLayer::setFillOpacity>; - result["fill-opacity-transition"] = &setTransition; - result["fill-color"] = &setProperty, &FillLayer::setFillColor>; - result["fill-color-transition"] = &setTransition; - result["fill-outline-color"] = &setProperty, &FillLayer::setFillOutlineColor>; - result["fill-outline-color-transition"] = &setTransition; - result["fill-translate"] = &setProperty>, &FillLayer::setFillTranslate>; - result["fill-translate-transition"] = &setTransition; - result["fill-translate-anchor"] = &setProperty, &FillLayer::setFillTranslateAnchor>; - result["fill-translate-anchor-transition"] = &setTransition; - result["fill-pattern"] = &setProperty, &FillLayer::setFillPattern>; - result["fill-pattern-transition"] = &setTransition; - - result["line-opacity"] = &setProperty, &LineLayer::setLineOpacity>; - result["line-opacity-transition"] = &setTransition; - result["line-color"] = &setProperty, &LineLayer::setLineColor>; - result["line-color-transition"] = &setTransition; - result["line-translate"] = &setProperty>, &LineLayer::setLineTranslate>; - result["line-translate-transition"] = &setTransition; - result["line-translate-anchor"] = &setProperty, &LineLayer::setLineTranslateAnchor>; - result["line-translate-anchor-transition"] = &setTransition; - result["line-width"] = &setProperty, &LineLayer::setLineWidth>; - result["line-width-transition"] = &setTransition; - result["line-gap-width"] = &setProperty, &LineLayer::setLineGapWidth>; - result["line-gap-width-transition"] = &setTransition; - result["line-offset"] = &setProperty, &LineLayer::setLineOffset>; - result["line-offset-transition"] = &setTransition; - result["line-blur"] = &setProperty, &LineLayer::setLineBlur>; - result["line-blur-transition"] = &setTransition; - result["line-dasharray"] = &setProperty>, &LineLayer::setLineDasharray>; - result["line-dasharray-transition"] = &setTransition; - result["line-pattern"] = &setProperty, &LineLayer::setLinePattern>; - result["line-pattern-transition"] = &setTransition; - - result["icon-opacity"] = &setProperty, &SymbolLayer::setIconOpacity>; - result["icon-opacity-transition"] = &setTransition; - result["icon-color"] = &setProperty, &SymbolLayer::setIconColor>; - result["icon-color-transition"] = &setTransition; - result["icon-halo-color"] = &setProperty, &SymbolLayer::setIconHaloColor>; - result["icon-halo-color-transition"] = &setTransition; - result["icon-halo-width"] = &setProperty, &SymbolLayer::setIconHaloWidth>; - result["icon-halo-width-transition"] = &setTransition; - result["icon-halo-blur"] = &setProperty, &SymbolLayer::setIconHaloBlur>; - result["icon-halo-blur-transition"] = &setTransition; - result["icon-translate"] = &setProperty>, &SymbolLayer::setIconTranslate>; - result["icon-translate-transition"] = &setTransition; - result["icon-translate-anchor"] = &setProperty, &SymbolLayer::setIconTranslateAnchor>; - result["icon-translate-anchor-transition"] = &setTransition; - result["text-opacity"] = &setProperty, &SymbolLayer::setTextOpacity>; - result["text-opacity-transition"] = &setTransition; - result["text-color"] = &setProperty, &SymbolLayer::setTextColor>; - result["text-color-transition"] = &setTransition; - result["text-halo-color"] = &setProperty, &SymbolLayer::setTextHaloColor>; - result["text-halo-color-transition"] = &setTransition; - result["text-halo-width"] = &setProperty, &SymbolLayer::setTextHaloWidth>; - result["text-halo-width-transition"] = &setTransition; - result["text-halo-blur"] = &setProperty, &SymbolLayer::setTextHaloBlur>; - result["text-halo-blur-transition"] = &setTransition; - result["text-translate"] = &setProperty>, &SymbolLayer::setTextTranslate>; - result["text-translate-transition"] = &setTransition; - result["text-translate-anchor"] = &setProperty, &SymbolLayer::setTextTranslateAnchor>; - result["text-translate-anchor-transition"] = &setTransition; - - result["circle-radius"] = &setProperty, &CircleLayer::setCircleRadius>; - result["circle-radius-transition"] = &setTransition; - result["circle-color"] = &setProperty, &CircleLayer::setCircleColor>; - result["circle-color-transition"] = &setTransition; - result["circle-blur"] = &setProperty, &CircleLayer::setCircleBlur>; - result["circle-blur-transition"] = &setTransition; - result["circle-opacity"] = &setProperty, &CircleLayer::setCircleOpacity>; - result["circle-opacity-transition"] = &setTransition; - result["circle-translate"] = &setProperty>, &CircleLayer::setCircleTranslate>; - result["circle-translate-transition"] = &setTransition; - result["circle-translate-anchor"] = &setProperty, &CircleLayer::setCircleTranslateAnchor>; - result["circle-translate-anchor-transition"] = &setTransition; - result["circle-pitch-scale"] = &setProperty, &CircleLayer::setCirclePitchScale>; - result["circle-pitch-scale-transition"] = &setTransition; - result["circle-pitch-alignment"] = &setProperty, &CircleLayer::setCirclePitchAlignment>; - result["circle-pitch-alignment-transition"] = &setTransition; - result["circle-stroke-width"] = &setProperty, &CircleLayer::setCircleStrokeWidth>; - result["circle-stroke-width-transition"] = &setTransition; - result["circle-stroke-color"] = &setProperty, &CircleLayer::setCircleStrokeColor>; - result["circle-stroke-color-transition"] = &setTransition; - result["circle-stroke-opacity"] = &setProperty, &CircleLayer::setCircleStrokeOpacity>; - result["circle-stroke-opacity-transition"] = &setTransition; - - result["fill-extrusion-opacity"] = &setProperty, &FillExtrusionLayer::setFillExtrusionOpacity>; - result["fill-extrusion-opacity-transition"] = &setTransition; - result["fill-extrusion-color"] = &setProperty, &FillExtrusionLayer::setFillExtrusionColor>; - result["fill-extrusion-color-transition"] = &setTransition; - result["fill-extrusion-translate"] = &setProperty>, &FillExtrusionLayer::setFillExtrusionTranslate>; - result["fill-extrusion-translate-transition"] = &setTransition; - result["fill-extrusion-translate-anchor"] = &setProperty, &FillExtrusionLayer::setFillExtrusionTranslateAnchor>; - result["fill-extrusion-translate-anchor-transition"] = &setTransition; - result["fill-extrusion-pattern"] = &setProperty, &FillExtrusionLayer::setFillExtrusionPattern>; - result["fill-extrusion-pattern-transition"] = &setTransition; - result["fill-extrusion-height"] = &setProperty, &FillExtrusionLayer::setFillExtrusionHeight>; - result["fill-extrusion-height-transition"] = &setTransition; - result["fill-extrusion-base"] = &setProperty, &FillExtrusionLayer::setFillExtrusionBase>; - result["fill-extrusion-base-transition"] = &setTransition; - - result["raster-opacity"] = &setProperty, &RasterLayer::setRasterOpacity>; - result["raster-opacity-transition"] = &setTransition; - result["raster-hue-rotate"] = &setProperty, &RasterLayer::setRasterHueRotate>; - result["raster-hue-rotate-transition"] = &setTransition; - result["raster-brightness-min"] = &setProperty, &RasterLayer::setRasterBrightnessMin>; - result["raster-brightness-min-transition"] = &setTransition; - result["raster-brightness-max"] = &setProperty, &RasterLayer::setRasterBrightnessMax>; - result["raster-brightness-max-transition"] = &setTransition; - result["raster-saturation"] = &setProperty, &RasterLayer::setRasterSaturation>; - result["raster-saturation-transition"] = &setTransition; - result["raster-contrast"] = &setProperty, &RasterLayer::setRasterContrast>; - result["raster-contrast-transition"] = &setTransition; - result["raster-fade-duration"] = &setProperty, &RasterLayer::setRasterFadeDuration>; - result["raster-fade-duration-transition"] = &setTransition; - - result["background-color"] = &setProperty, &BackgroundLayer::setBackgroundColor>; - result["background-color-transition"] = &setTransition; - result["background-pattern"] = &setProperty, &BackgroundLayer::setBackgroundPattern>; - result["background-pattern-transition"] = &setTransition; - result["background-opacity"] = &setProperty, &BackgroundLayer::setBackgroundOpacity>; - result["background-opacity-transition"] = &setTransition; - - 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 19c9f70538b..00000000000 --- 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 - -<% for (const layer of locals.layers) { -%> -#include _layer.hpp> -<% } -%> - -#include - -namespace mbgl { -namespace style { -namespace conversion { - -template -auto makeLayoutPropertySetters() { - std::unordered_map> result; - - result["visibility"] = &setVisibility; - -<% for (const layer of locals.layers) { -%> -<% for (const property of layer.layoutProperties) { -%> - result["<%- property.name %>"] = &setPropertyLayer, <%- propertyValueType(property) %>, &<%- camelize(layer.type) %>Layer::set<%- camelize(property.name) %>>; -<% } -%> - -<% } -%> - return result; -} - -template -auto makePaintPropertySetters() { - std::unordered_map> result; - -<% for (const layer of locals.layers) { -%> -<% for (const property of layer.paintProperties) { -%> - result["<%- property.name %>"] = &setPropertyLayer, <%- propertyValueType(property) %>, &<%- camelize(layer.type) %>Layer::set<%- camelize(property.name) %>>; - result["<%- property.name %>-transition"] = &setTransitionLayer, &<%- 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 7036b03822c..044c45862d0 100644 --- a/include/mbgl/style/conversion/position.hpp +++ b/include/mbgl/style/conversion/position.hpp @@ -2,9 +2,6 @@ #include #include -#include - -#include namespace mbgl { namespace style { @@ -12,16 +9,7 @@ namespace conversion { template <> struct Converter { - template - optional operator()(const V& value, Error& error) const { - optional> spherical = convert>(value, error); - - if (!spherical) { - return {}; - } - - return Position(*spherical); - } + optional 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 f8937da07d6..c7f971ec91f 100644 --- a/include/mbgl/style/conversion/property_value.hpp +++ b/include/mbgl/style/conversion/property_value.hpp @@ -11,8 +11,7 @@ namespace conversion { template struct Converter> { - template - optional> operator()(const V& value, Error& error) const { + optional> operator()(const Convertible& value, Error& error) const { if (isUndefined(value)) { return PropertyValue(); } else if (isObject(value)) { diff --git a/include/mbgl/style/conversion/source.hpp b/include/mbgl/style/conversion/source.hpp index e0563ac10b8..2cf2e36da4e 100644 --- a/include/mbgl/style/conversion/source.hpp +++ b/include/mbgl/style/conversion/source.hpp @@ -1,16 +1,9 @@ #pragma once #include -#include -#include -#include -#include #include -#include -#include -#include -#include -#include + +#include namespace mbgl { namespace style { @@ -19,170 +12,7 @@ namespace conversion { template <> struct Converter> { public: - - template - optional> 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 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 - optional> convertURLOrTileset(const V& value, Error& error) const { - auto urlVal = objectMember(value, "url"); - if (!urlVal) { - optional tileset = convert(value, error); - if (!tileset) { - return {}; - } - return { *tileset }; - } - - optional url = toString(*urlVal); - if (!url) { - error = { "source url must be a string" }; - return {}; - } - - return { *url }; - } - - template - optional> convertRasterSource(const std::string& id, - const V& value, - Error& error) const { - optional> urlOrTileset = convertURLOrTileset(value, error); - if (!urlOrTileset) { - return {}; - } - - uint16_t tileSize = util::tileSize; - auto tileSizeValue = objectMember(value, "tileSize"); - if (tileSizeValue) { - optional size = toNumber(*tileSizeValue); - if (!size || *size < 0 || *size > std::numeric_limits::max()) { - error = { "invalid tileSize" }; - return {}; - } - tileSize = *size; - } - - return { std::make_unique(id, std::move(*urlOrTileset), tileSize) }; - } - - template - optional> convertVectorSource(const std::string& id, - const V& value, - Error& error) const { - optional> urlOrTileset = convertURLOrTileset(value, error); - if (!urlOrTileset) { - return {}; - } - - return { std::make_unique(id, std::move(*urlOrTileset)) }; - } - - template - optional> 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 options = convert(value, error); - if (!options) { - return {}; - } - - auto result = std::make_unique(id, *options); - - if (isObject(*dataValue)) { - optional geoJSON = convert(*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 - optional> 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 coordinates; - for (std::size_t i=0; i < 4; i++) { - auto latLng = conversion::convert(arrayMember(*coordinatesValue,i), error); - if (!latLng) { - return {}; - } - coordinates[i] = *latLng; - } - auto result = std::make_unique(id, coordinates); - result->setURL(*urlString); - - return { std::move(result) }; - } + optional> 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 377170aa6a0..1fb4acf70d7 100644 --- a/include/mbgl/style/conversion/tileset.hpp +++ b/include/mbgl/style/conversion/tileset.hpp @@ -10,70 +10,7 @@ namespace conversion { template <> struct Converter { public: - template - optional 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 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 scheme = toString(*schemeValue); - if (scheme && *scheme == "tms") { - result.scheme = Tileset::Scheme::TMS; - } - } - - auto minzoomValue = objectMember(value, "minzoom"); - if (minzoomValue) { - optional minzoom = toNumber(*minzoomValue); - if (!minzoom || *minzoom < 0 || *minzoom > std::numeric_limits::max()) { - error = { "invalid minzoom" }; - return {}; - } - result.zoomRange.min = *minzoom; - } - - auto maxzoomValue = objectMember(value, "maxzoom"); - if (maxzoomValue) { - optional maxzoom = toNumber(*maxzoomValue); - if (!maxzoom || *maxzoom < 0 || *maxzoom > std::numeric_limits::max()) { - error = { "invalid maxzoom" }; - return {}; - } - result.zoomRange.max = *maxzoom; - } - - auto attributionValue = objectMember(value, "attribution"); - if (attributionValue) { - optional attribution = toString(*attributionValue); - if (!attribution) { - error = { "source attribution must be a string" }; - return {}; - } - result.attribution = std::move(*attribution); - } - - return result; - } + optional 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 de8834d578f..0563f39ac3c 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 { public: - template - optional 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 operator()(const Convertible& value, Error& error) const; }; } // namespace conversion diff --git a/include/mbgl/util/tileset.hpp b/include/mbgl/util/tileset.hpp index 61aa47d4eaa..5a03e1a9dad 100644 --- a/include/mbgl/util/tileset.hpp +++ b/include/mbgl/util/tileset.hpp @@ -3,6 +3,7 @@ #include #include +#include #include #include #include diff --git a/platform/android/config.cmake b/platform/android/config.cmake index 8dd537d36e1..db75b850f1b 100644 --- a/platform/android/config.cmake +++ b/platform/android/config.cmake @@ -146,7 +146,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 diff --git a/platform/android/src/style/android_conversion.hpp b/platform/android/src/style/android_conversion.hpp index 082fe411e2d..510a9f8444d 100644 --- a/platform/android/src/style/android_conversion.hpp +++ b/platform/android/src/style/android_conversion.hpp @@ -4,8 +4,9 @@ #include #include -#include #include +#include +#include #include @@ -13,89 +14,105 @@ namespace mbgl { namespace style { namespace conversion { -inline bool isUndefined(const mbgl::android::Value& value) { - return value.isNull(); -} +template <> +class ConversionTraits { +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 objectMember(const mbgl::android::Value& value, const char* key) { - mbgl::android::Value member = value.get(key); + static optional 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 + static optional eachMember(const mbgl::android::Value&, Fn&&) { + // TODO + mbgl::Log::Warning(mbgl::Event::Android, "eachMember not implemented"); return {}; } -} -template -optional eachMember(const mbgl::android::Value&, Fn&&) { - // TODO - mbgl::Log::Warning(mbgl::Event::Android, "eachMember not implemented"); - return {}; -} + static optional toBool(const mbgl::android::Value& value) { + if (value.isBool()) { + return value.toBool(); + } else { + return {}; + } + } -inline optional toBool(const mbgl::android::Value& value) { - if (value.isBool()) { - return value.toBool(); - } else { - return {}; + static optional toNumber(const mbgl::android::Value& value) { + if (value.isNumber()) { + auto num = value.toFloat(); + return num; + } else { + return {}; + } } -} -inline optional toNumber(const mbgl::android::Value& value) { - if (value.isNumber()) { - auto num = value.toFloat(); - return num; - } else { - return {}; + static optional toDouble(const mbgl::android::Value& value) { + if (value.isNumber()) { + return value.toDouble(); + } else { + return {}; + } } -} -inline optional toDouble(const mbgl::android::Value& value) { - if (value.isNumber()) { - return value.toDouble(); - } else { - return {}; + static optional toString(const mbgl::android::Value& value) { + if (value.isString()) { + return value.toString(); + } else { + return {}; + } } -} -inline optional toString(const mbgl::android::Value& value) { - if (value.isString()) { - return value.toString(); - } else { - return {}; + static optional 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 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 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 +optional convert(mbgl::android::Value&& value, Error& error, Args&&...args) { + return convert(Convertible(std::move(value)), error, std::forward(args)...); } } // namespace conversion diff --git a/platform/android/src/style/conversion/filter.hpp b/platform/android/src/style/conversion/filter.hpp index 1f0abcf3a4d..c154e88e7c1 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 toFilter(jni::JNIEnv& env, jni::Array> jfilter) { mbgl::optional filter; if (jfilter) { - Value filterValue(env, jfilter); mbgl::style::conversion::Error error; - auto converted = mbgl::style::conversion::convert(filterValue, error); + auto converted = mbgl::style::conversion::convert(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 748fe7361eb..00000000000 --- a/platform/android/src/style/conversion/geojson.hpp +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -namespace mbgl { -namespace style { -namespace conversion { - -template <> -optional Converter::operator()(const mbgl::android::Value& value, Error& error) const { - if(value.isNull() || !value.isString()) { - error = { "no json data found" }; - return {}; - } - - return convert(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 00ef913d418..92c1182a63b 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 convertURLOrTileset(const Value& value) { +inline variant 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 = convert(value, error); + optional tileset = convert(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 02a1f0be821..31032b117f7 100644 --- a/platform/android/src/style/layers/layer.cpp +++ b/platform/android/src/style/layers/layer.cpp @@ -4,11 +4,20 @@ #include #include +#include #include +#include +#include +#include +#include +#include +#include +#include #include // Java -> C++ conversion #include +#include #include #include @@ -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 error = mbgl::style::conversion::setLayoutProperty(layer, jni::Make(env, jname), value); + optional error = mbgl::style::conversion::setLayoutProperty(layer, jni::Make(env, jname), Value(env, jvalue)); if (error) { mbgl::Log::Error(mbgl::Event::JNI, "Error setting property: " + jni::Make(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 error = mbgl::style::conversion::setPaintProperty(layer, jni::Make(env, jname), value); + optional error = mbgl::style::conversion::setPaintProperty(layer, jni::Make(env, jname), Value(env, jvalue)); if (error) { mbgl::Log::Error(mbgl::Event::JNI, "Error setting property: " + jni::Make(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 converted = convert(wrapped, error); + optional converted = convert(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 90ef851ebaf..4468b453f37 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 +#include +#include // C++ -> Java conversion #include "../../conversion/conversion.hpp" #include "../../conversion/collection.hpp" #include "../../geojson/conversion/feature.hpp" #include "../conversion/url_or_tileset.hpp" -#include -#include #include @@ -29,7 +29,7 @@ namespace android { return style::GeoJSONOptions(); } Error error; - optional result = convert(Value(env, options), error); + optional result = convert(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 converted = convert(Value(env, json), error); + optional converted = convert(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 e1cd81d7fdb..70bdea66774 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 7464bae8321..2057b934547 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/src/MGLConversion.h b/platform/darwin/src/MGLConversion.h index d6363b28eb1..0d18d4e716a 100644 --- a/platform/darwin/src/MGLConversion.h +++ b/platform/darwin/src/MGLConversion.h @@ -1,9 +1,4 @@ -#import - -#include #include -#include -#include 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` 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 { +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::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::max(), @"Array length out of bounds."); - return length; -} + static optional 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 + static optional 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 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 eachMember(const NSObject*, Fn&&) + static optional 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 toDouble(const Holder& holder) { + const id value = holder.value; + if (_isNumber(value)) { + return ((NSNumber *)value).doubleValue; + } else { + return {}; + } + } -inline optional toBool(const id value) { - if (_isBool(value)) { - return ((NSNumber *)value).boolValue; - } else { - return {}; + static optional toString(const Holder& holder) { + const id value = holder.value; + if (_isString(value)) { + return std::string(static_cast([value UTF8String])); + } else { + return {}; + } } -} -inline optional toNumber(const id value) { - if (_isNumber(value)) { - return ((NSNumber *)value).floatValue; - } else { - return {}; + static optional 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(*toNumber(holder)) }; + } else { + return {}; + } } -} -inline optional toDouble(const id value) { - if (_isNumber(value)) { - return ((NSNumber *)value).doubleValue; - } else { + static optional toGeoJSON(const Holder& holder, Error& error) { + error = { "toGeoJSON not implemented" }; return {}; } -} -inline optional toString(const id value) { - if (_isString(value)) { - return std::string(static_cast([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 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(*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 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 2155c657bd0..5914e0a2aaf 100644 --- a/platform/darwin/src/MGLStyleValue_Private.h +++ b/platform/darwin/src/MGLStyleValue_Private.h @@ -124,9 +124,9 @@ class MGLStyleValueTransformer { if ([value isKindOfClass:[MGLConstantStyleValue class]]) { return toMBGLConstantValue((MGLConstantStyleValue *)value); } else if ([value isKindOfClass:[MGLStyleFunction class]]) { - auto rawValue = toRawStyleSpecValue((MGLStyleFunction *) value); mbgl::style::conversion::Error error; - auto result = mbgl::style::conversion::convert>(rawValue, error); + auto result = mbgl::style::conversion::convert>( + mbgl::style::conversion::makeConvertible(toRawStyleSpecValue((MGLStyleFunction *) value)), error); NSCAssert(result, @(error.message.c_str())); return *result; } else { diff --git a/platform/node/src/node_conversion.hpp b/platform/node/src/node_conversion.hpp index d2667455485..7c5bbf43862 100644 --- a/platform/node/src/node_conversion.hpp +++ b/platform/node/src/node_conversion.hpp @@ -9,111 +9,136 @@ #include #include #include +#include namespace mbgl { namespace style { namespace conversion { -inline bool isUndefined(v8::Local value) { - Nan::HandleScope scope; - return value->IsUndefined() || value->IsNull(); -} +template <> +class ConversionTraits> { +public: + static bool isUndefined(const v8::Local& value) { + Nan::HandleScope scope; + return value->IsUndefined() || value->IsNull(); + } -inline bool isArray(v8::Local value) { - Nan::HandleScope scope; - return value->IsArray(); -} + static bool isArray(const v8::Local& value) { + Nan::HandleScope scope; + return value->IsArray(); + } -inline std::size_t arrayLength(v8::Local value) { - Nan::HandleScope scope; - return value.As()->Length(); -} + static std::size_t arrayLength(const v8::Local& value) { + Nan::HandleScope scope; + // const_cast because v8::Local::As is not marked const until node v8.0 + v8::Local array = const_cast&>(value).As(); + return array->Length(); + } -inline v8::Local arrayMember(v8::Local value, std::size_t i) { - Nan::EscapableHandleScope scope; - return scope.Escape(Nan::Get(value.As(), i).ToLocalChecked()); -} + static v8::Local arrayMember(const v8::Local& value, std::size_t i) { + Nan::EscapableHandleScope scope; + // const_cast because v8::Local::As is not marked const until node v8.0 + v8::Local array = const_cast&>(value).As(); + return scope.Escape(Nan::Get(array, i).ToLocalChecked()); + } -inline bool isObject(v8::Local value) { - Nan::HandleScope scope; - return value->IsObject() && !value->IsArray(); -} + static bool isObject(const v8::Local& value) { + Nan::HandleScope scope; + return value->IsObject() && !value->IsArray(); + } -inline optional> objectMember(v8::Local value, const char * name) { - Nan::EscapableHandleScope scope; - if (!Nan::Has(Nan::To(value).ToLocalChecked(), Nan::New(name).ToLocalChecked()).FromJust()) { - return {}; + static optional> objectMember(const v8::Local& value, const char * name) { + Nan::EscapableHandleScope scope; + if (!Nan::Has(Nan::To(value).ToLocalChecked(), Nan::New(name).ToLocalChecked()).FromJust()) { + return {}; + } + Nan::MaybeLocal result = Nan::Get(Nan::To(value).ToLocalChecked(), Nan::New(name).ToLocalChecked()); + if (result.IsEmpty()) { + return {}; + } + return {scope.Escape(result.ToLocalChecked())}; } - Nan::MaybeLocal result = Nan::Get(Nan::To(value).ToLocalChecked(), Nan::New(name).ToLocalChecked()); - if (result.IsEmpty()) { + + template + static optional eachMember(const v8::Local& value, Fn&& fn) { + Nan::HandleScope scope; + v8::Local names = Nan::GetOwnPropertyNames(Nan::To(value).ToLocalChecked()).ToLocalChecked(); + for (uint32_t i = 0; i < names->Length(); ++i) { + v8::Local k = Nan::Get(names, i).ToLocalChecked(); + v8::Local v = Nan::Get(Nan::To(value).ToLocalChecked(), k).ToLocalChecked(); + optional result = fn(*Nan::Utf8String(k), std::move(v)); + if (result) { + return result; + } + } return {}; } - return scope.Escape(result.ToLocalChecked()); -} -template -optional eachMember(v8::Local value, Fn&& fn) { - Nan::HandleScope scope; - v8::Local names = Nan::GetOwnPropertyNames(Nan::To(value).ToLocalChecked()).ToLocalChecked(); - for (uint32_t i = 0; i < names->Length(); ++i) { - v8::Local k = Nan::Get(names, i).ToLocalChecked(); - v8::Local v = Nan::Get(Nan::To(value).ToLocalChecked(), k).ToLocalChecked(); - optional result = fn(*Nan::Utf8String(k), v); - if (result) { - return result; + static optional toBool(const v8::Local& value) { + Nan::HandleScope scope; + if (!value->IsBoolean()) { + return {}; } + return value->BooleanValue(); } - return {}; -} -inline optional toBool(v8::Local value) { - Nan::HandleScope scope; - if (!value->IsBoolean()) { - return {}; + static optional toNumber(const v8::Local& value) { + Nan::HandleScope scope; + if (!value->IsNumber()) { + return {}; + } + return value->NumberValue(); } - return value->BooleanValue(); -} -inline optional toNumber(v8::Local value) { - Nan::HandleScope scope; - if (!value->IsNumber()) { - return {}; + static optional toDouble(const v8::Local& value) { + Nan::HandleScope scope; + if (!value->IsNumber()) { + return {}; + } + return value->NumberValue(); } - return value->NumberValue(); -} -inline optional toDouble(v8::Local value) { - Nan::HandleScope scope; - if (!value->IsNumber()) { - return {}; + static optional toString(const v8::Local& value) { + Nan::HandleScope scope; + if (!value->IsString()) { + return {}; + } + return std::string(*Nan::Utf8String(value)); } - return value->NumberValue(); -} -inline optional toString(v8::Local value) { - Nan::HandleScope scope; - if (!value->IsString()) { - return {}; + static optional toValue(const v8::Local& 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 toValue(v8::Local 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 toGeoJSON(const v8::Local& 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 +optional convert(const v8::Local& value, Error& error, Args&&...args) { + return convert(Convertible(value), error, std::forward(args)...); } } // namespace conversion diff --git a/platform/node/src/node_geojson.hpp b/platform/node/src/node_geojson.hpp deleted file mode 100644 index 8a3927e2cfe..00000000000 --- a/platform/node/src/node_geojson.hpp +++ /dev/null @@ -1,17 +0,0 @@ -#include - -#include -namespace mbgl { -namespace style { -namespace conversion { - -template <> -optional Converter::operator()(const v8::Local& value, Error& error) const { - Nan::JSON JSON; - std::string string = *Nan::Utf8String(JSON.Stringify(value->ToObject()).ToLocalChecked()); - return convert(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 f344461f319..6deccf05bf9 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 #include @@ -10,6 +9,15 @@ #include #include #include + +#include +#include +#include +#include +#include +#include +#include + #include #include #include @@ -741,7 +749,7 @@ void NodeMap::SetLayoutProperty(const Nan::FunctionCallbackInfo& info return Nan::ThrowTypeError("Second argument must be a string"); } - mbgl::optional error = setLayoutProperty(*layer, *Nan::Utf8String(info[1]), info[2]); + mbgl::optional error = setLayoutProperty(*layer, *Nan::Utf8String(info[1]), Convertible(info[2])); if (error) { return Nan::ThrowTypeError(error->message.c_str()); } @@ -773,7 +781,7 @@ void NodeMap::SetPaintProperty(const Nan::FunctionCallbackInfo& info) return Nan::ThrowTypeError("Second argument must be a string"); } - mbgl::optional error = setPaintProperty(*layer, *Nan::Utf8String(info[1]), info[2]); + mbgl::optional error = setPaintProperty(*layer, *Nan::Utf8String(info[1]), Convertible(info[2])); if (error) { return Nan::ThrowTypeError(error->message.c_str()); } diff --git a/platform/qt/qt.cmake b/platform/qt/qt.cmake index 2346d7d8207..a7862c39364 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 diff --git a/platform/qt/src/qmapboxgl.cpp b/platform/qt/src/qmapboxgl.cpp index 074ef280aae..740bb90202c 100644 --- a/platform/qt/src/qmapboxgl.cpp +++ b/platform/qt/src/qmapboxgl.cpp @@ -14,7 +14,17 @@ #include #include #include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include #include #include #include diff --git a/platform/qt/src/qt_conversion.hpp b/platform/qt/src/qt_conversion.hpp index 40d7e5b9284..19b0cb54fc7 100644 --- a/platform/qt/src/qt_conversion.hpp +++ b/platform/qt/src/qt_conversion.hpp @@ -1,120 +1,145 @@ #pragma once #include -#include +#include #include -#include - -#include #include +#include +#include +#include "qt_geojson.hpp" namespace mbgl { namespace style { namespace conversion { -inline bool isUndefined(const QVariant& value) { - return value.isNull() || !value.isValid(); -} +template <> +class ConversionTraits { +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 objectMember(const QVariant& value, const char* key) { - auto map = value.toMap(); - auto iter = map.constFind(key); + static optional 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(const std::string&, const QVariant&)>; + template + static optional eachMember(const QVariant& value, Fn&& fn) { + auto map = value.toMap(); + auto iter = map.constBegin(); -optional eachMember(const QVariant& value, EachMemberFn&& fn) { - auto map = value.toMap(); - auto iter = map.constBegin(); + while (iter != map.constEnd()) { + optional result = fn(iter.key().toStdString(), QVariant(iter.value())); + if (result) { + return result; + } - while (iter != map.constEnd()) { - optional result = fn(iter.key().toStdString(), iter.value()); - if (result) { - return result; + ++iter; } - ++iter; + return {}; } - return {}; -} + static optional toBool(const QVariant& value) { + if (value.type() == QVariant::Bool) { + return value.toBool(); + } else { + return {}; + } + } -inline optional toBool(const QVariant& value) { - if (value.type() == QVariant::Bool) { - return value.toBool(); - } else { - return {}; + static optional toNumber(const QVariant& value) { + if (value.type() == QVariant::Int || value.type() == QVariant::Double) { + return value.toFloat(); + } else { + return {}; + } } -} -inline optional toNumber(const QVariant& value) { - if (value.type() == QVariant::Int || value.type() == QVariant::Double) { - return value.toFloat(); - } else { - return {}; + static optional toDouble(const QVariant& value) { + if (value.type() == QVariant::Int || value.type() == QVariant::Double) { + return value.toDouble(); + } else { + return {}; + } } -} -inline optional toDouble(const QVariant& value) { - if (value.type() == QVariant::Int || value.type() == QVariant::Double) { - return value.toDouble(); - } else { - return {}; + + static optional toString(const QVariant& value) { + if (value.type() == QVariant::String) { + return value.toString().toStdString(); + } else if (value.type() == QVariant::Color) { + return value.value().name().toStdString(); + } else { + return {}; + } } -} -inline optional toString(const QVariant& value) { - if (value.type() == QVariant::String) { - return value.toString().toStdString(); - } else if (value.type() == QVariant::Color) { - return value.value().name().toStdString(); - } else { - return {}; + static optional 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().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 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().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 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()) }; + } 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 +optional convert(const QVariant& value, Error& error, Args&&...args) { + return convert(Convertible(value), error, std::forward(args)...); } } // namespace conversion diff --git a/platform/qt/src/qt_geojson.cpp b/platform/qt/src/qt_geojson.cpp new file mode 100644 index 00000000000..80377de64d6 --- /dev/null +++ b/platform/qt/src/qt_geojson.cpp @@ -0,0 +1,166 @@ +#include "qt_geojson.hpp" +#include +#include +#include + +namespace QMapbox { + +mbgl::Point asMapboxGLPoint(const QMapbox::Coordinate &coordinate) { + return mbgl::Point { coordinate.second, coordinate.first }; +} + +mbgl::MultiPoint asMapboxGLMultiPoint(const QMapbox::Coordinates &multiPoint) { + mbgl::MultiPoint mbglMultiPoint; + mbglMultiPoint.reserve(multiPoint.size()); + for (const auto &point: multiPoint) { + mbglMultiPoint.emplace_back(asMapboxGLPoint(point)); + } + return mbglMultiPoint; +}; + +mbgl::LineString asMapboxGLLineString(const QMapbox::Coordinates &lineString) { + mbgl::LineString mbglLineString; + mbglLineString.reserve(lineString.size()); + for (const auto &coordinate : lineString) { + mbglLineString.emplace_back(asMapboxGLPoint(coordinate)); + } + return mbglLineString; +}; + +mbgl::MultiLineString asMapboxGLMultiLineString(const QMapbox::CoordinatesCollection &multiLineString) { + mbgl::MultiLineString mbglMultiLineString; + mbglMultiLineString.reserve(multiLineString.size()); + for (const auto &lineString : multiLineString) { + mbglMultiLineString.emplace_back(std::forward>(asMapboxGLLineString(lineString))); + } + return mbglMultiLineString; +}; + +mbgl::Polygon asMapboxGLPolygon(const QMapbox::CoordinatesCollection &polygon) { + mbgl::Polygon mbglPolygon; + mbglPolygon.reserve(polygon.size()); + for (const auto &linearRing : polygon) { + mbgl::LinearRing 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 asMapboxGLMultiPolygon(const QMapbox::CoordinatesCollections &multiPolygon) { + mbgl::MultiPolygon mbglMultiPolygon; + mbglMultiPolygon.reserve(multiPolygon.size()); + for (const auto &polygon : multiPolygon) { + mbglMultiPolygon.emplace_back(std::forward>(asMapboxGLPolygon(polygon))); + } + return mbglMultiPolygon; +}; + +mbgl::Value asMapboxGLPropertyValue(const QVariant &value) { + auto valueList = [](const QVariantList &list) { + std::vector 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 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 a6958b7edcf..a9c10272ab1 100644 --- a/platform/qt/src/qt_geojson.hpp +++ b/platform/qt/src/qt_geojson.hpp @@ -1,7 +1,8 @@ #pragma once #include -#include +#include +#include #include @@ -13,187 +14,14 @@ namespace QMapbox { -mbgl::Point asMapboxGLPoint(const QMapbox::Coordinate &coordinate) { - return mbgl::Point { coordinate.second, coordinate.first }; -} - -mbgl::MultiPoint asMapboxGLMultiPoint(const QMapbox::Coordinates &multiPoint) { - mbgl::MultiPoint mbglMultiPoint; - mbglMultiPoint.reserve(multiPoint.size()); - for (const auto &point: multiPoint) { - mbglMultiPoint.emplace_back(asMapboxGLPoint(point)); - } - return mbglMultiPoint; -}; - -mbgl::LineString asMapboxGLLineString(const QMapbox::Coordinates &lineString) { - mbgl::LineString mbglLineString; - mbglLineString.reserve(lineString.size()); - for (const auto &coordinate : lineString) { - mbglLineString.emplace_back(asMapboxGLPoint(coordinate)); - } - return mbglLineString; -}; - -mbgl::MultiLineString asMapboxGLMultiLineString(const QMapbox::CoordinatesCollection &multiLineString) { - mbgl::MultiLineString mbglMultiLineString; - mbglMultiLineString.reserve(multiLineString.size()); - for (const auto &lineString : multiLineString) { - mbglMultiLineString.emplace_back(std::forward>(asMapboxGLLineString(lineString))); - } - return mbglMultiLineString; -}; - -mbgl::Polygon asMapboxGLPolygon(const QMapbox::CoordinatesCollection &polygon) { - mbgl::Polygon mbglPolygon; - mbglPolygon.reserve(polygon.size()); - for (const auto &linearRing : polygon) { - mbgl::LinearRing 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 asMapboxGLMultiPolygon(const QMapbox::CoordinatesCollections &multiPolygon) { - mbgl::MultiPolygon mbglMultiPolygon; - mbglMultiPolygon.reserve(multiPolygon.size()); - for (const auto &polygon : multiPolygon) { - mbglMultiPolygon.emplace_back(std::forward>(asMapboxGLPolygon(polygon))); - } - return mbglMultiPolygon; -}; - -mbgl::Value asMapboxGLPropertyValue(const QVariant &value) { - auto valueList = [](const QVariantList &list) { - std::vector 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 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 asMapboxGLPoint(const QMapbox::Coordinate &coordinate); +mbgl::MultiPoint asMapboxGLMultiPoint(const QMapbox::Coordinates &multiPoint); +mbgl::LineString asMapboxGLLineString(const QMapbox::Coordinates &lineString); +mbgl::MultiLineString asMapboxGLMultiLineString(const QMapbox::CoordinatesCollection &multiLineString); +mbgl::Polygon asMapboxGLPolygon(const QMapbox::CoordinatesCollection &polygon); +mbgl::MultiPolygon 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 Converter::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()) }; - } else if (value.type() != QVariant::ByteArray) { - error = { "JSON data must be in QByteArray" }; - return {}; - } - - QByteArray data = value.toByteArray(); - return convert(std::string(data.constData(), data.size()), error); -} - -} // namespace conversion -} // namespace style -} // namespace mbgl diff --git a/scripts/generate-style-code.js b/scripts/generate-style-code.js index fe9a1a906b3..443f0c7cc9a 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/style/conversion/constant.cpp b/src/mbgl/style/conversion/constant.cpp new file mode 100644 index 00000000000..e837c4e70bb --- /dev/null +++ b/src/mbgl/style/conversion/constant.cpp @@ -0,0 +1,94 @@ +#include + +namespace mbgl { +namespace style { +namespace conversion { + +optional Converter::operator()(const Convertible& value, Error& error) const { + optional converted = toBool(value); + if (!converted) { + error = { "value must be a boolean" }; + return {}; + } + return *converted; +} + +optional Converter::operator()(const Convertible& value, Error& error) const { + optional converted = toNumber(value); + if (!converted) { + error = { "value must be a number" }; + return {}; + } + return *converted; +} + +optional Converter::operator()(const Convertible& value, Error& error) const { + optional converted = toString(value); + if (!converted) { + error = { "value must be a string" }; + return {}; + } + return *converted; +} + +optional Converter::operator()(const Convertible& value, Error& error) const { + optional string = toString(value); + if (!string) { + error = { "value must be a string" }; + return {}; + } + + optional color = Color::parse(*string); + if (!color) { + error = { "value must be a valid color" }; + return {}; + } + + return *color; +} + +optional> Converter>::operator()(const Convertible& value, Error& error) const { + if (!isArray(value)) { + error = { "value must be an array" }; + return {}; + } + + std::vector result; + result.reserve(arrayLength(value)); + + for (std::size_t i = 0; i < arrayLength(value); ++i) { + optional number = toNumber(arrayMember(value, i)); + if (!number) { + error = { "value must be an array of numbers" }; + return {}; + } + result.push_back(*number); + } + + return result; +} + +optional> Converter>::operator()(const Convertible& value, Error& error) const { + if (!isArray(value)) { + error = { "value must be an array" }; + return {}; + } + + std::vector result; + result.reserve(arrayLength(value)); + + for (std::size_t i = 0; i < arrayLength(value); ++i) { + optional 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 00000000000..9b2be3381ec --- /dev/null +++ b/src/mbgl/style/conversion/coordinate.cpp @@ -0,0 +1,29 @@ +#include + +namespace mbgl { +namespace style { +namespace conversion { + +optional Converter::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 latitude = toDouble(arrayMember(value, 1)); + optional 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 00000000000..bb7bb6ea980 --- /dev/null +++ b/src/mbgl/style/conversion/filter.cpp @@ -0,0 +1,248 @@ +#include +#include + +namespace mbgl { +namespace style { +namespace conversion { + +static optional normalizeValue(const optional& value, Error& error) { + if (!value) { + error = { "filter expression value must be a boolean, number, or string" }; + return {}; + } else { + return *value; + } +} + +static optional toFeatureType(const Convertible& value, Error& error) { + optional 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 toFeatureIdentifier(const Convertible& value, Error& error) { + optional 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 { return { t }; }, + [] ( int64_t t) -> optional { return { t }; }, + [] ( double t) -> optional { return { t }; }, + [] (const std::string& t) -> optional { return { t }; }, + [&] (const auto&) -> optional { + error = { "filter expression value must be a boolean, number, or string" }; + return {}; + }); + } +} + +template +optional convertUnaryFilter(const Convertible& value, Error& error) { + if (arrayLength(value) < 2) { + error = { "filter expression must have 2 elements" }; + return {}; + } + + optional 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 +optional convertEqualityFilter(const Convertible& value, Error& error) { + if (arrayLength(value) < 3) { + error = { "filter expression must have 3 elements" }; + return {}; + } + + optional key = toString(arrayMember(value, 1)); + if (!key) { + error = { "filter expression key must be a string" }; + return {}; + } + + if (*key == "$type") { + optional filterValue = toFeatureType(arrayMember(value, 2), error); + if (!filterValue) { + return {}; + } + + return { TypeFilterType { *filterValue } }; + + } else if (*key == "$id") { + optional filterValue = toFeatureIdentifier(arrayMember(value, 2), error); + if (!filterValue) { + return {}; + } + + return { IdentifierFilterType { *filterValue } }; + + } else { + optional filterValue = normalizeValue(toValue(arrayMember(value, 2)), error); + if (!filterValue) { + return {}; + } + + return { FilterType { *key, *filterValue } }; + } +} + +template +optional convertBinaryFilter(const Convertible& value, Error& error) { + if (arrayLength(value) < 3) { + error = { "filter expression must have 3 elements" }; + return {}; + } + + optional key = toString(arrayMember(value, 1)); + if (!key) { + error = { "filter expression key must be a string" }; + return {}; + } + + optional filterValue = normalizeValue(toValue(arrayMember(value, 2)), error); + if (!filterValue) { + return {}; + } + + return { FilterType { *key, *filterValue } }; +} + +template +optional convertSetFilter(const Convertible& value, Error& error) { + if (arrayLength(value) < 2) { + error = { "filter expression must at least 2 elements" }; + return {}; + } + + optional key = toString(arrayMember(value, 1)); + if (!key) { + error = { "filter expression key must be a string" }; + return {}; + } + + if (*key == "$type") { + std::vector values; + for (std::size_t i = 2; i < arrayLength(value); ++i) { + optional filterValue = toFeatureType(arrayMember(value, i), error); + if (!filterValue) { + return {}; + } + values.push_back(*filterValue); + } + + return { TypeFilterType { std::move(values) } }; + + } else if (*key == "$id") { + std::vector values; + for (std::size_t i = 2; i < arrayLength(value); ++i) { + optional filterValue = toFeatureIdentifier(arrayMember(value, i), error); + if (!filterValue) { + return {}; + } + values.push_back(*filterValue); + } + + return { IdentifierFilterType { std::move(values) } }; + + } else { + std::vector values; + for (std::size_t i = 2; i < arrayLength(value); ++i) { + optional filterValue = normalizeValue(toValue(arrayMember(value, i)), error); + if (!filterValue) { + return {}; + } + values.push_back(*filterValue); + } + + return { FilterType { *key, std::move(values) } }; + } +} + +template +optional convertCompoundFilter(const Convertible& value, Error& error) { + std::vector filters; + for (std::size_t i = 1; i < arrayLength(value); ++i) { + optional element = convert(arrayMember(value, i), error); + if (!element) { + return {}; + } + filters.push_back(*element); + } + + return { FilterType { std::move(filters) } }; +} + +optional Converter::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 op = toString(arrayMember(value, 0)); + if (!op) { + error = { "filter operator must be a string" }; + return {}; + } + + if (*op == "==") { + return convertEqualityFilter(value, error); + } else if (*op == "!=") { + return convertEqualityFilter(value, error); + } else if (*op == ">") { + return convertBinaryFilter(value, error); + } else if (*op == ">=") { + return convertBinaryFilter(value, error); + } else if (*op == "<") { + return convertBinaryFilter(value, error); + } else if (*op == "<=") { + return convertBinaryFilter(value, error); + } else if (*op == "in") { + return convertSetFilter(value, error); + } else if (*op == "!in") { + return convertSetFilter(value, error); + } else if (*op == "all") { + return convertCompoundFilter(value, error); + } else if (*op == "any") { + return convertCompoundFilter(value, error); + } else if (*op == "none") { + return convertCompoundFilter(value, error); + } else if (*op == "has") { + return convertUnaryFilter(value, error); + } else if (*op == "!has") { + return convertUnaryFilter(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 8103e9014a5..e39a1a80ebc 100644 --- a/src/mbgl/style/conversion/geojson.cpp +++ b/src/mbgl/style/conversion/geojson.cpp @@ -1,26 +1,16 @@ #include #include -#include - -#include -#include namespace mbgl { namespace style { namespace conversion { -optional Converter::operator()(const std::string& value, Error& error) const { - return convertJSON(value, error); +optional Converter::operator()(const Convertible& value, Error& error) const { + return toGeoJSON(value, error); } -template <> -optional Converter::operator()(const JSValue& value, Error& error) const { - try { - return mapbox::geojson::convert(value); - } catch (const std::exception& ex) { - error = { ex.what() }; - return {}; - } +optional parseGeoJSON(const std::string& value, Error& error) { + return convertJSON(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 00000000000..a2c5ed88160 --- /dev/null +++ b/src/mbgl/style/conversion/geojson_options.cpp @@ -0,0 +1,85 @@ +#include + +namespace mbgl { +namespace style { +namespace conversion { + +optional Converter::operator()(const Convertible& value, Error& error) const { + GeoJSONOptions options; + + const auto minzoomValue = objectMember(value, "minzoom"); + if (minzoomValue) { + if (toNumber(*minzoomValue)) { + options.minzoom = static_cast(*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(*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(*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(*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(*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(*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/json.hpp b/src/mbgl/style/conversion/json.hpp index 0817ac09dfa..7dd2378f6b9 100644 --- a/src/mbgl/style/conversion/json.hpp +++ b/src/mbgl/style/conversion/json.hpp @@ -20,7 +20,7 @@ optional convertJSON(const std::string& json, Error& error, Args&&...args) { return {}; } - return convert(document, error, std::forward(args)...); + return convert(document, error, std::forward(args)...); } } // namespace conversion diff --git a/src/mbgl/style/conversion/layer.cpp b/src/mbgl/style/conversion/layer.cpp new file mode 100644 index 00000000000..0ca582f8dce --- /dev/null +++ b/src/mbgl/style/conversion/layer.cpp @@ -0,0 +1,206 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace mbgl { +namespace style { +namespace conversion { + +optional 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 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 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 +optional> 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 source = toString(*sourceValue); + if (!source) { + error = { "layer source must be a string" }; + return {}; + } + + std::unique_ptr layer = std::make_unique(id, *source); + + auto sourceLayerValue = objectMember(value, "source-layer"); + if (sourceLayerValue) { + optional 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 = convert(*filterValue, error); + if (!filter) { + return {}; + } + layer->setFilter(*filter); + } + + return { std::move(layer) }; +} + +static optional> 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 source = toString(*sourceValue); + if (!source) { + error = { "layer source must be a string" }; + return {}; + } + + return { std::make_unique(id, *source) }; +} + +static optional> convertBackgroundLayer(const std::string& id, const Convertible&, Error&) { + return { std::make_unique(id) }; +} + +optional> Converter>::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 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 type = toString(*typeValue); + if (!type) { + error = { "layer type must be a string" }; + return {}; + } + + optional> converted; + + if (*type == "fill") { + converted = convertVectorLayer(*id, value, error); + } else if (*type == "fill-extrusion") { + converted = convertVectorLayer(*id, value, error); + } else if (*type == "line") { + converted = convertVectorLayer(*id, value, error); + } else if (*type == "circle") { + converted = convertVectorLayer(*id, value, error); + } else if (*type == "symbol") { + converted = convertVectorLayer(*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 = std::move(*converted); + + auto minzoomValue = objectMember(value, "minzoom"); + if (minzoomValue) { + optional minzoom = toNumber(*minzoomValue); + if (!minzoom) { + error = { "minzoom must be numeric" }; + return {}; + } + layer->setMinZoom(*minzoom); + } + + auto maxzoomValue = objectMember(value, "maxzoom"); + if (maxzoomValue) { + optional 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_ = eachMember(*layoutValue, [&] (const std::string& k, const Convertible& v) { + return setLayoutProperty(*layer, k, v); + }); + if (error_) { + error = *error_; + return {}; + } + } + + optional 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 00000000000..f521f743868 --- /dev/null +++ b/src/mbgl/style/conversion/light.cpp @@ -0,0 +1,115 @@ +#include +#include +#include +#include + +namespace mbgl { +namespace style { +namespace conversion { + +optional Converter::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> convertedAnchor = + convert>(*anchor, error); + + if (convertedAnchor) { + light.setAnchor(*convertedAnchor); + } else { + return {}; + } + } + + const auto anchorTransition = objectMember(value, "anchor-transition"); + if (anchorTransition) { + optional transition = + convert(*anchorTransition, error); + if (transition) { + light.setAnchorTransition(*transition); + } else { + return {}; + } + } + + const auto color = objectMember(value, "color"); + if (color) { + optional> convertedColor = + convert>(*color, error); + + if (convertedColor) { + light.setColor(*convertedColor); + } else { + return {}; + } + } + + const auto colorTransition = objectMember(value, "color-transition"); + if (colorTransition) { + optional transition = + convert(*colorTransition, error); + if (transition) { + light.setColorTransition(*transition); + } else { + return {}; + } + } + + const auto position = objectMember(value, "position"); + if (position) { + optional> convertedPosition = + convert>(*position, error); + + if (convertedPosition) { + light.setPosition(*convertedPosition); + } else { + return {}; + } + } + + const auto positionTransition = objectMember(value, "position-transition"); + if (positionTransition) { + optional transition = + convert(*positionTransition, error); + if (transition) { + light.setPositionTransition(*transition); + } else { + return {}; + } + } + + const auto intensity = objectMember(value, "intensity"); + if (intensity) { + optional> convertedIntensity = + convert>(*intensity, error); + + if (convertedIntensity) { + light.setIntensity(*convertedIntensity); + } else { + return {}; + } + } + + const auto intensityTransition = objectMember(value, "intensity-transition"); + if (intensityTransition) { + optional transition = + convert(*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 00000000000..074d7eb7303 --- /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 + +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace mbgl { +namespace style { +namespace conversion { + +inline auto makeLayoutPropertySetters() { + std::unordered_map result; + + result["visibility"] = &setVisibility; + + + result["line-cap"] = &setProperty, &LineLayer::setLineCap>; + result["line-join"] = &setProperty, &LineLayer::setLineJoin>; + result["line-miter-limit"] = &setProperty, &LineLayer::setLineMiterLimit>; + result["line-round-limit"] = &setProperty, &LineLayer::setLineRoundLimit>; + + result["symbol-placement"] = &setProperty, &SymbolLayer::setSymbolPlacement>; + result["symbol-spacing"] = &setProperty, &SymbolLayer::setSymbolSpacing>; + result["symbol-avoid-edges"] = &setProperty, &SymbolLayer::setSymbolAvoidEdges>; + result["icon-allow-overlap"] = &setProperty, &SymbolLayer::setIconAllowOverlap>; + result["icon-ignore-placement"] = &setProperty, &SymbolLayer::setIconIgnorePlacement>; + result["icon-optional"] = &setProperty, &SymbolLayer::setIconOptional>; + result["icon-rotation-alignment"] = &setProperty, &SymbolLayer::setIconRotationAlignment>; + result["icon-size"] = &setProperty, &SymbolLayer::setIconSize>; + result["icon-text-fit"] = &setProperty, &SymbolLayer::setIconTextFit>; + result["icon-text-fit-padding"] = &setProperty>, &SymbolLayer::setIconTextFitPadding>; + result["icon-image"] = &setProperty, &SymbolLayer::setIconImage>; + result["icon-rotate"] = &setProperty, &SymbolLayer::setIconRotate>; + result["icon-padding"] = &setProperty, &SymbolLayer::setIconPadding>; + result["icon-keep-upright"] = &setProperty, &SymbolLayer::setIconKeepUpright>; + result["icon-offset"] = &setProperty>, &SymbolLayer::setIconOffset>; + result["icon-anchor"] = &setProperty, &SymbolLayer::setIconAnchor>; + result["icon-pitch-alignment"] = &setProperty, &SymbolLayer::setIconPitchAlignment>; + result["text-pitch-alignment"] = &setProperty, &SymbolLayer::setTextPitchAlignment>; + result["text-rotation-alignment"] = &setProperty, &SymbolLayer::setTextRotationAlignment>; + result["text-field"] = &setProperty, &SymbolLayer::setTextField>; + result["text-font"] = &setProperty>, &SymbolLayer::setTextFont>; + result["text-size"] = &setProperty, &SymbolLayer::setTextSize>; + result["text-max-width"] = &setProperty, &SymbolLayer::setTextMaxWidth>; + result["text-line-height"] = &setProperty, &SymbolLayer::setTextLineHeight>; + result["text-letter-spacing"] = &setProperty, &SymbolLayer::setTextLetterSpacing>; + result["text-justify"] = &setProperty, &SymbolLayer::setTextJustify>; + result["text-anchor"] = &setProperty, &SymbolLayer::setTextAnchor>; + result["text-max-angle"] = &setProperty, &SymbolLayer::setTextMaxAngle>; + result["text-rotate"] = &setProperty, &SymbolLayer::setTextRotate>; + result["text-padding"] = &setProperty, &SymbolLayer::setTextPadding>; + result["text-keep-upright"] = &setProperty, &SymbolLayer::setTextKeepUpright>; + result["text-transform"] = &setProperty, &SymbolLayer::setTextTransform>; + result["text-offset"] = &setProperty>, &SymbolLayer::setTextOffset>; + result["text-allow-overlap"] = &setProperty, &SymbolLayer::setTextAllowOverlap>; + result["text-ignore-placement"] = &setProperty, &SymbolLayer::setTextIgnorePlacement>; + result["text-optional"] = &setProperty, &SymbolLayer::setTextOptional>; + + + + + + return result; +} + +inline auto makePaintPropertySetters() { + std::unordered_map result; + + result["fill-antialias"] = &setProperty, &FillLayer::setFillAntialias>; + result["fill-antialias-transition"] = &setTransition; + result["fill-opacity"] = &setProperty, &FillLayer::setFillOpacity>; + result["fill-opacity-transition"] = &setTransition; + result["fill-color"] = &setProperty, &FillLayer::setFillColor>; + result["fill-color-transition"] = &setTransition; + result["fill-outline-color"] = &setProperty, &FillLayer::setFillOutlineColor>; + result["fill-outline-color-transition"] = &setTransition; + result["fill-translate"] = &setProperty>, &FillLayer::setFillTranslate>; + result["fill-translate-transition"] = &setTransition; + result["fill-translate-anchor"] = &setProperty, &FillLayer::setFillTranslateAnchor>; + result["fill-translate-anchor-transition"] = &setTransition; + result["fill-pattern"] = &setProperty, &FillLayer::setFillPattern>; + result["fill-pattern-transition"] = &setTransition; + + result["line-opacity"] = &setProperty, &LineLayer::setLineOpacity>; + result["line-opacity-transition"] = &setTransition; + result["line-color"] = &setProperty, &LineLayer::setLineColor>; + result["line-color-transition"] = &setTransition; + result["line-translate"] = &setProperty>, &LineLayer::setLineTranslate>; + result["line-translate-transition"] = &setTransition; + result["line-translate-anchor"] = &setProperty, &LineLayer::setLineTranslateAnchor>; + result["line-translate-anchor-transition"] = &setTransition; + result["line-width"] = &setProperty, &LineLayer::setLineWidth>; + result["line-width-transition"] = &setTransition; + result["line-gap-width"] = &setProperty, &LineLayer::setLineGapWidth>; + result["line-gap-width-transition"] = &setTransition; + result["line-offset"] = &setProperty, &LineLayer::setLineOffset>; + result["line-offset-transition"] = &setTransition; + result["line-blur"] = &setProperty, &LineLayer::setLineBlur>; + result["line-blur-transition"] = &setTransition; + result["line-dasharray"] = &setProperty>, &LineLayer::setLineDasharray>; + result["line-dasharray-transition"] = &setTransition; + result["line-pattern"] = &setProperty, &LineLayer::setLinePattern>; + result["line-pattern-transition"] = &setTransition; + + result["icon-opacity"] = &setProperty, &SymbolLayer::setIconOpacity>; + result["icon-opacity-transition"] = &setTransition; + result["icon-color"] = &setProperty, &SymbolLayer::setIconColor>; + result["icon-color-transition"] = &setTransition; + result["icon-halo-color"] = &setProperty, &SymbolLayer::setIconHaloColor>; + result["icon-halo-color-transition"] = &setTransition; + result["icon-halo-width"] = &setProperty, &SymbolLayer::setIconHaloWidth>; + result["icon-halo-width-transition"] = &setTransition; + result["icon-halo-blur"] = &setProperty, &SymbolLayer::setIconHaloBlur>; + result["icon-halo-blur-transition"] = &setTransition; + result["icon-translate"] = &setProperty>, &SymbolLayer::setIconTranslate>; + result["icon-translate-transition"] = &setTransition; + result["icon-translate-anchor"] = &setProperty, &SymbolLayer::setIconTranslateAnchor>; + result["icon-translate-anchor-transition"] = &setTransition; + result["text-opacity"] = &setProperty, &SymbolLayer::setTextOpacity>; + result["text-opacity-transition"] = &setTransition; + result["text-color"] = &setProperty, &SymbolLayer::setTextColor>; + result["text-color-transition"] = &setTransition; + result["text-halo-color"] = &setProperty, &SymbolLayer::setTextHaloColor>; + result["text-halo-color-transition"] = &setTransition; + result["text-halo-width"] = &setProperty, &SymbolLayer::setTextHaloWidth>; + result["text-halo-width-transition"] = &setTransition; + result["text-halo-blur"] = &setProperty, &SymbolLayer::setTextHaloBlur>; + result["text-halo-blur-transition"] = &setTransition; + result["text-translate"] = &setProperty>, &SymbolLayer::setTextTranslate>; + result["text-translate-transition"] = &setTransition; + result["text-translate-anchor"] = &setProperty, &SymbolLayer::setTextTranslateAnchor>; + result["text-translate-anchor-transition"] = &setTransition; + + result["circle-radius"] = &setProperty, &CircleLayer::setCircleRadius>; + result["circle-radius-transition"] = &setTransition; + result["circle-color"] = &setProperty, &CircleLayer::setCircleColor>; + result["circle-color-transition"] = &setTransition; + result["circle-blur"] = &setProperty, &CircleLayer::setCircleBlur>; + result["circle-blur-transition"] = &setTransition; + result["circle-opacity"] = &setProperty, &CircleLayer::setCircleOpacity>; + result["circle-opacity-transition"] = &setTransition; + result["circle-translate"] = &setProperty>, &CircleLayer::setCircleTranslate>; + result["circle-translate-transition"] = &setTransition; + result["circle-translate-anchor"] = &setProperty, &CircleLayer::setCircleTranslateAnchor>; + result["circle-translate-anchor-transition"] = &setTransition; + result["circle-pitch-scale"] = &setProperty, &CircleLayer::setCirclePitchScale>; + result["circle-pitch-scale-transition"] = &setTransition; + result["circle-pitch-alignment"] = &setProperty, &CircleLayer::setCirclePitchAlignment>; + result["circle-pitch-alignment-transition"] = &setTransition; + result["circle-stroke-width"] = &setProperty, &CircleLayer::setCircleStrokeWidth>; + result["circle-stroke-width-transition"] = &setTransition; + result["circle-stroke-color"] = &setProperty, &CircleLayer::setCircleStrokeColor>; + result["circle-stroke-color-transition"] = &setTransition; + result["circle-stroke-opacity"] = &setProperty, &CircleLayer::setCircleStrokeOpacity>; + result["circle-stroke-opacity-transition"] = &setTransition; + + result["fill-extrusion-opacity"] = &setProperty, &FillExtrusionLayer::setFillExtrusionOpacity>; + result["fill-extrusion-opacity-transition"] = &setTransition; + result["fill-extrusion-color"] = &setProperty, &FillExtrusionLayer::setFillExtrusionColor>; + result["fill-extrusion-color-transition"] = &setTransition; + result["fill-extrusion-translate"] = &setProperty>, &FillExtrusionLayer::setFillExtrusionTranslate>; + result["fill-extrusion-translate-transition"] = &setTransition; + result["fill-extrusion-translate-anchor"] = &setProperty, &FillExtrusionLayer::setFillExtrusionTranslateAnchor>; + result["fill-extrusion-translate-anchor-transition"] = &setTransition; + result["fill-extrusion-pattern"] = &setProperty, &FillExtrusionLayer::setFillExtrusionPattern>; + result["fill-extrusion-pattern-transition"] = &setTransition; + result["fill-extrusion-height"] = &setProperty, &FillExtrusionLayer::setFillExtrusionHeight>; + result["fill-extrusion-height-transition"] = &setTransition; + result["fill-extrusion-base"] = &setProperty, &FillExtrusionLayer::setFillExtrusionBase>; + result["fill-extrusion-base-transition"] = &setTransition; + + result["raster-opacity"] = &setProperty, &RasterLayer::setRasterOpacity>; + result["raster-opacity-transition"] = &setTransition; + result["raster-hue-rotate"] = &setProperty, &RasterLayer::setRasterHueRotate>; + result["raster-hue-rotate-transition"] = &setTransition; + result["raster-brightness-min"] = &setProperty, &RasterLayer::setRasterBrightnessMin>; + result["raster-brightness-min-transition"] = &setTransition; + result["raster-brightness-max"] = &setProperty, &RasterLayer::setRasterBrightnessMax>; + result["raster-brightness-max-transition"] = &setTransition; + result["raster-saturation"] = &setProperty, &RasterLayer::setRasterSaturation>; + result["raster-saturation-transition"] = &setTransition; + result["raster-contrast"] = &setProperty, &RasterLayer::setRasterContrast>; + result["raster-contrast-transition"] = &setTransition; + result["raster-fade-duration"] = &setProperty, &RasterLayer::setRasterFadeDuration>; + result["raster-fade-duration-transition"] = &setTransition; + + result["background-color"] = &setProperty, &BackgroundLayer::setBackgroundColor>; + result["background-color-transition"] = &setTransition; + result["background-pattern"] = &setProperty, &BackgroundLayer::setBackgroundPattern>; + result["background-pattern-transition"] = &setTransition; + result["background-opacity"] = &setProperty, &BackgroundLayer::setBackgroundOpacity>; + result["background-opacity-transition"] = &setTransition; + + 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 00000000000..2975cb19f2d --- /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 + +<% for (const layer of locals.layers) { -%> +#include _layer.hpp> +<% } -%> + +#include + +namespace mbgl { +namespace style { +namespace conversion { + +inline auto makeLayoutPropertySetters() { + std::unordered_map 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 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 00000000000..702d250dbf1 --- /dev/null +++ b/src/mbgl/style/conversion/position.cpp @@ -0,0 +1,22 @@ +#include +#include + +#include + +namespace mbgl { +namespace style { +namespace conversion { + +optional Converter::operator()(const Convertible& value, Error& error) const { + optional> spherical = convert>(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 similarity index 76% rename from include/mbgl/style/conversion/property_setter.hpp rename to src/mbgl/style/conversion/property_setter.hpp index 759c4512cca..9e382b9c38d 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 -using PropertySetter = optional (*) (Layer&, const V&); +using PropertySetter = optional (*) (Layer&, const Convertible&); -template -optional setProperty(Layer& layer, const V& value) { +template +optional setProperty(Layer& layer, const Convertible& value) { auto* typedLayer = layer.as(); if (!typedLayer) { return Error { "layer doesn't support this property" }; @@ -33,8 +32,8 @@ optional setProperty(Layer& layer, const V& value) { return {}; } -template -optional setTransition(Layer& layer, const V& value) { +template +optional setTransition(Layer& layer, const Convertible& value) { auto* typedLayer = layer.as(); if (!typedLayer) { return Error { "layer doesn't support this property" }; @@ -50,8 +49,7 @@ optional setTransition(Layer& layer, const V& value) { return {}; } -template -optional setVisibility(Layer& layer, const V& value) { +inline optional 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 00000000000..c10d0babcf8 --- /dev/null +++ b/src/mbgl/style/conversion/source.cpp @@ -0,0 +1,175 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace mbgl { +namespace style { +namespace conversion { + +// A tile source can either specify a URL to TileJSON, or inline TileJSON. +static optional> convertURLOrTileset(const Convertible& value, Error& error) { + auto urlVal = objectMember(value, "url"); + if (!urlVal) { + optional tileset = convert(value, error); + if (!tileset) { + return {}; + } + return { *tileset }; + } + + optional url = toString(*urlVal); + if (!url) { + error = { "source url must be a string" }; + return {}; + } + + return { *url }; +} + +static optional> convertRasterSource(const std::string& id, + const Convertible& value, + Error& error) { + optional> urlOrTileset = convertURLOrTileset(value, error); + if (!urlOrTileset) { + return {}; + } + + uint16_t tileSize = util::tileSize; + auto tileSizeValue = objectMember(value, "tileSize"); + if (tileSizeValue) { + optional size = toNumber(*tileSizeValue); + if (!size || *size < 0 || *size > std::numeric_limits::max()) { + error = { "invalid tileSize" }; + return {}; + } + tileSize = *size; + } + + return { std::make_unique(id, std::move(*urlOrTileset), tileSize) }; +} + +static optional> convertVectorSource(const std::string& id, + const Convertible& value, + Error& error) { + optional> urlOrTileset = convertURLOrTileset(value, error); + if (!urlOrTileset) { + return {}; + } + + return { std::make_unique(id, std::move(*urlOrTileset)) }; +} + +static optional> 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 options = convert(value, error); + if (!options) { + return {}; + } + + auto result = std::make_unique(id, *options); + + if (isObject(*dataValue)) { + optional geoJSON = convert(*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> 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 coordinates; + for (std::size_t i=0; i < 4; i++) { + auto latLng = conversion::convert(arrayMember(*coordinatesValue,i), error); + if (!latLng) { + return {}; + } + coordinates[i] = *latLng; + } + auto result = std::make_unique(id, coordinates); + result->setURL(*urlString); + + return { std::move(result) }; +} + +optional> Converter>::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 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 00000000000..b9383c41b81 --- /dev/null +++ b/src/mbgl/style/conversion/tileset.cpp @@ -0,0 +1,73 @@ +#include + +namespace mbgl { +namespace style { +namespace conversion { + +optional Converter::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 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 scheme = toString(*schemeValue); + if (scheme && *scheme == "tms") { + result.scheme = Tileset::Scheme::TMS; + } + } + + auto minzoomValue = objectMember(value, "minzoom"); + if (minzoomValue) { + optional minzoom = toNumber(*minzoomValue); + if (!minzoom || *minzoom < 0 || *minzoom > std::numeric_limits::max()) { + error = { "invalid minzoom" }; + return {}; + } + result.zoomRange.min = *minzoom; + } + + auto maxzoomValue = objectMember(value, "maxzoom"); + if (maxzoomValue) { + optional maxzoom = toNumber(*maxzoomValue); + if (!maxzoom || *maxzoom < 0 || *maxzoom > std::numeric_limits::max()) { + error = { "invalid maxzoom" }; + return {}; + } + result.zoomRange.max = *maxzoom; + } + + auto attributionValue = objectMember(value, "attribution"); + if (attributionValue) { + optional 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 00000000000..8a60c5bfd8e --- /dev/null +++ b/src/mbgl/style/conversion/transition_options.cpp @@ -0,0 +1,40 @@ +#include + +namespace mbgl { +namespace style { +namespace conversion { + +optional Converter::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/parser.cpp b/src/mbgl/style/parser.cpp index a83897dbf53..10fce339860 100644 --- a/src/mbgl/style/parser.cpp +++ b/src/mbgl/style/parser.cpp @@ -1,11 +1,13 @@ #include #include +#include #include #include #include #include #include #include +#include #include #include @@ -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> 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> converted = conversion::convert>(value, error); diff --git a/src/mbgl/style/rapidjson_conversion.hpp b/src/mbgl/style/rapidjson_conversion.hpp index 48a764ccb4d..79bd9c928b1 100644 --- a/src/mbgl/style/rapidjson_conversion.hpp +++ b/src/mbgl/style/rapidjson_conversion.hpp @@ -1,103 +1,125 @@ #pragma once #include -#include #include +#include +#include + 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 { +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 -optional eachMember(const JSValue& value, Fn&& fn) { - assert(value.IsObject()); - for (const auto& property : value.GetObject()) { - optional result = - fn({ property.name.GetString(), property.name.GetStringLength() }, property.value); - if (result) { - return result; + static optional objectMember(const JSValue* value, const char * name) { + if (!value->HasMember(name)) { + return optional(); } + const JSValue* const& member = &(*value)[name]; + return {member}; } - return {}; -} -inline optional toBool(const JSValue& value) { - if (!value.IsBool()) { + template + static optional eachMember(const JSValue* value, Fn&& fn) { + assert(value->IsObject()); + for (const auto& property : value->GetObject()) { + optional result = + fn({ property.name.GetString(), property.name.GetStringLength() }, &property.value); + if (result) { + return result; + } + } return {}; } - return value.GetBool(); -} -inline optional toNumber(const JSValue& value) { - if (!value.IsNumber()) { - return {}; + static optional toBool(const JSValue* value) { + if (!value->IsBool()) { + return {}; + } + return value->GetBool(); } - return value.GetDouble(); -} -inline optional toDouble(const JSValue& value) { - if (!value.IsNumber()) { - return {}; + static optional toNumber(const JSValue* value) { + if (!value->IsNumber()) { + return {}; + } + return value->GetDouble(); } - return value.GetDouble(); -} -inline optional toString(const JSValue& value) { - if (!value.IsString()) { - return {}; + static optional toDouble(const JSValue* value) { + if (!value->IsNumber()) { + return {}; + } + return value->GetDouble(); + } + + static optional toString(const JSValue* value) { + if (!value->IsString()) { + return {}; + } + return {{ value->GetString(), value->GetStringLength() }}; } - return {{ value.GetString(), value.GetStringLength() }}; -} -inline optional toValue(const JSValue& value) { - switch (value.GetType()) { - case rapidjson::kNullType: - case rapidjson::kFalseType: - return { false }; + static optional 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 toGeoJSON(const JSValue* value, Error& error) { + try { + return mapbox::geojson::convert(*value); + } catch (const std::exception& ex) { + error = { ex.what() }; return {}; + } } +}; + +template +optional convert(const JSValue& value, Error& error, Args&&...args) { + return convert(Convertible(&value), error, std::forward(args)...); } } // namespace conversion } // namespace style } // namespace mbgl + diff --git a/test/src/mbgl/test/conversion_stubs.hpp b/test/src/mbgl/test/conversion_stubs.hpp deleted file mode 100644 index 30395ddb973..00000000000 --- a/test/src/mbgl/test/conversion_stubs.hpp +++ /dev/null @@ -1,124 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -#include -#include - -namespace mbgl { -namespace style { -namespace conversion { - -class Value; -using ValueMap = std::unordered_map; -using ValueVector = std::vector; -class Value : public mbgl::variant, - mapbox::util::recursive_wrapper> { - using variant::variant; -}; - -inline bool isUndefined(const Value&) { - // Variant is always intialized - return false; -} - -inline bool isArray(const Value& value) { - return value.is>(); -} - -inline std::size_t arrayLength(const Value& value) { - return value.get>().get().size(); -} - -inline Value arrayMember(const Value& value, std::size_t i) { - return value.get>().get()[i]; -} - -inline bool isObject(const Value& value) { - return value.is>(); -} - -inline optional objectMember(const Value& value, const char* key) { - auto map = value.get(); - auto iter = map.find(key); - - if (iter != map.end()) { - return iter->second; - } else { - return {}; - } -} - -using EachMemberFn = std::function(const std::string&, const Value&)>; - -optional eachMember(const Value& value, EachMemberFn&& fn) { - auto map = value.get(); - auto iter = map.begin(); - - while (iter != map.end()) { - optional result = fn(iter->first, iter->second); - if (result) { - return result; - } - - ++iter; - } - - return {}; -} - -inline optional toBool(const Value& value) { - if (value.is()) { - return value.get(); - } else { - return {}; - } -} - -inline optional toNumber(const Value& value) { - if (value.is()) { - return value.get(); - } else { - return {}; - } - return {}; -} - - -inline optional toDouble(const Value& value) { - if (value.is()) { - return value.get(); - } - return {}; -} - -inline optional toString(const Value& value) { - if (value.is()) { - return value.get(); - } else { - return {}; - } -} - -inline optional toValue(const Value& value) { - if (value.is()) { - return { value.get() }; - } else if (value.is()) { - return { value.get() }; - } else if (value.is()) { - return { double(value.get()) }; - } else { - return {}; - } -} - -} // namespace conversion -} // namespace style -} // namespace mbgl