From c7c4f990366739e66bcea6ed8cb2539268527c43 Mon Sep 17 00:00:00 2001 From: Alexander Shalamov Date: Mon, 4 Mar 2019 08:17:06 +0200 Subject: [core] Add ["text-section"] expression New expression evaluates to formatted section id (string | number) that can be used within decision expressions, such as 'match' or 'case', so that each formatted section can have different paint properties. --- include/mbgl/style/expression/dsl.hpp | 1 + include/mbgl/style/expression/expression.hpp | 15 ++- .../mbgl/style/expression/format_expression.hpp | 15 +-- include/mbgl/style/expression/formatted.hpp | 22 ++++- include/mbgl/style/property_expression.hpp | 103 +++++++-------------- src/core-files.json | 1 + src/mbgl/style/conversion/function.cpp | 2 +- src/mbgl/style/expression/compound_expression.cpp | 11 +++ src/mbgl/style/expression/dsl.cpp | 10 +- src/mbgl/style/expression/format_expression.cpp | 62 ++++++++++--- src/mbgl/style/expression/formatted.cpp | 42 ++++++--- src/mbgl/style/expression/is_constant.cpp | 9 +- src/mbgl/style/property_expression.cpp | 68 ++++++++++++++ 13 files changed, 240 insertions(+), 121 deletions(-) create mode 100644 src/mbgl/style/property_expression.cpp diff --git a/include/mbgl/style/expression/dsl.hpp b/include/mbgl/style/expression/dsl.hpp index bd94a765e7..fcbca25941 100644 --- a/include/mbgl/style/expression/dsl.hpp +++ b/include/mbgl/style/expression/dsl.hpp @@ -82,6 +82,7 @@ std::unique_ptr concat(std::vector> inpu std::unique_ptr format(const char* value); std::unique_ptr format(std::unique_ptr); +std::unique_ptr textSection(); } // namespace dsl } // namespace expression diff --git a/include/mbgl/style/expression/expression.hpp b/include/mbgl/style/expression/expression.hpp index 97b143b3d9..d868b17e00 100644 --- a/include/mbgl/style/expression/expression.hpp +++ b/include/mbgl/style/expression/expression.hpp @@ -25,18 +25,25 @@ public: class EvaluationContext { public: - EvaluationContext(float zoom_) : zoom(zoom_), feature(nullptr) {} - EvaluationContext(GeometryTileFeature const * feature_) : zoom(optional()), feature(feature_) {} + EvaluationContext() = default; + explicit EvaluationContext(float zoom_) : zoom(zoom_) {} + EvaluationContext(GeometryTileFeature const * feature_) : feature(feature_) {} EvaluationContext(float zoom_, GeometryTileFeature const * feature_) : zoom(zoom_), feature(feature_) {} EvaluationContext(optional zoom_, GeometryTileFeature const * feature_, optional colorRampParameter_) : zoom(std::move(zoom_)), feature(feature_), colorRampParameter(std::move(colorRampParameter_)) {} - + + EvaluationContext& withFormattedSection(const Value* formattedSection_) noexcept { + formattedSection = formattedSection_; + return *this; + }; + optional zoom; - GeometryTileFeature const * feature; + GeometryTileFeature const * feature = nullptr; optional colorRampParameter; + const Value* formattedSection = nullptr; }; template diff --git a/include/mbgl/style/expression/format_expression.hpp b/include/mbgl/style/expression/format_expression.hpp index b00674a88e..09feaf4819 100644 --- a/include/mbgl/style/expression/format_expression.hpp +++ b/include/mbgl/style/expression/format_expression.hpp @@ -1,11 +1,7 @@ #pragma once #include -#include #include -#include - -#include namespace mbgl { namespace style { @@ -14,16 +10,18 @@ namespace expression { struct FormatExpressionSection { FormatExpressionSection(std::unique_ptr text_, optional> fontScale_, - optional> textFont_); + optional> textFont_, + optional> sectionID_); std::shared_ptr text; optional> fontScale; optional> textFont; + optional> sectionID; }; -class FormatExpression : public Expression { +class FormatExpression final : public Expression { public: - FormatExpression(std::vector sections); + explicit FormatExpression(std::vector sections); EvaluationResult evaluate(const EvaluationContext&) const override; static ParseResult parse(const mbgl::style::conversion::Convertible&, ParsingContext&); @@ -42,9 +40,6 @@ public: std::string getOperator() const override { return "format"; } private: std::vector sections; - std::unique_ptr text; - optional> fontScale; - optional> textFont; }; } // namespace expression diff --git a/include/mbgl/style/expression/formatted.hpp b/include/mbgl/style/expression/formatted.hpp index 9e7e7308cb..28964de941 100644 --- a/include/mbgl/style/expression/formatted.hpp +++ b/include/mbgl/style/expression/formatted.hpp @@ -12,15 +12,33 @@ namespace mbgl { namespace style { namespace expression { +extern const char* const kFormattedSectionFontScale; +extern const char* const kFormattedSectionTextFont; +extern const char* const kFormattedSectionID; + +using FormattedSectionID = variant; + +template +optional toFormattedSectionID(const Variant& variant) { + return variant.match( + [] (double t) -> FormattedSectionID { return t; }, + [] (const std::string& t) -> FormattedSectionID { return t;}, + [] (auto&) -> optional { return nullopt; }); +} + struct FormattedSection { - FormattedSection(std::string text_, optional fontScale_, optional fontStack_) + FormattedSection(std::string text_, optional fontScale_, + optional fontStack_, optional id_) : text(std::move(text_)) , fontScale(std::move(fontScale_)) , fontStack(std::move(fontStack_)) + , id(std::move(id_)) {} + std::string text; optional fontScale; optional fontStack; + optional id; }; class Formatted { @@ -28,7 +46,7 @@ public: Formatted() = default; Formatted(const char* plainU8String) { - sections.emplace_back(std::string(plainU8String), nullopt, nullopt); + sections.emplace_back(std::string(plainU8String), nullopt, nullopt, nullopt); } Formatted(std::vector sections_) diff --git a/include/mbgl/style/property_expression.hpp b/include/mbgl/style/property_expression.hpp index b198de02b2..43e80dfac4 100644 --- a/include/mbgl/style/property_expression.hpp +++ b/include/mbgl/style/property_expression.hpp @@ -1,7 +1,6 @@ #pragma once #include -#include #include #include #include @@ -11,46 +10,38 @@ namespace mbgl { namespace style { -template -class PropertyExpression { +class PropertyExpressionBase { public: - // Second parameter to be used only for conversions from legacy functions. - PropertyExpression(std::unique_ptr expression_, optional defaultValue_ = {}) - : expression(std::move(expression_)), - defaultValue(std::move(defaultValue_)), - zoomCurve(expression::findZoomCurveChecked(expression.get())) { - } + explicit PropertyExpressionBase(std::unique_ptr); - bool isZoomConstant() const { return expression::isZoomConstant(*expression); } - bool isFeatureConstant() const { return expression::isFeatureConstant(*expression); } + bool isZoomConstant() const noexcept; + bool isFeatureConstant() const noexcept; + bool canEvaluateWith(const expression::EvaluationContext&) const noexcept; + float interpolationFactor(const Range&, const float) const noexcept; + Range getCoveringStops(const float, const float) const noexcept; + const expression::Expression& getExpression() const noexcept; - T evaluate(float zoom) const { - assert(!expression::isZoomConstant(*expression)); - assert(expression::isFeatureConstant(*expression)); - const expression::EvaluationResult result = expression->evaluate(expression::EvaluationContext(zoom, nullptr)); - if (result) { - const optional typed = expression::fromExpressionValue(*result); - return typed ? *typed : defaultValue ? *defaultValue : T(); - } - return defaultValue ? *defaultValue : T(); - } + bool useIntegerZoom = false; - template - T evaluate(const Feature& feature, T finalDefaultValue) const { - assert(expression::isZoomConstant(*expression)); - assert(!expression::isFeatureConstant(*expression)); - const expression::EvaluationResult result = expression->evaluate(expression::EvaluationContext(&feature)); - if (result) { - const optional typed = expression::fromExpressionValue(*result); - return typed ? *typed : defaultValue ? *defaultValue : finalDefaultValue; - } - return defaultValue ? *defaultValue : finalDefaultValue; +protected: + std::shared_ptr expression; + variant zoomCurve; + bool isZoomConstant_; + bool isFeatureConstant_; +}; + +template +class PropertyExpression final : public PropertyExpressionBase { +public: + // Second parameter to be used only for conversions from legacy functions. + PropertyExpression(std::unique_ptr expression_, optional defaultValue_ = {}) + : PropertyExpressionBase(std::move(expression_)), + defaultValue(std::move(defaultValue_)) { } - template - T evaluate(float zoom, const Feature& feature, T finalDefaultValue) const { - assert(!expression::isFeatureConstant(*expression)); - const expression::EvaluationResult result = expression->evaluate(expression::EvaluationContext({zoom}, &feature)); + T evaluate(const expression::EvaluationContext& context, T finalDefaultValue = T()) const { + assert(canEvaluateWith(context)); + const expression::EvaluationResult result = expression->evaluate(context); if (result) { const optional typed = expression::fromExpressionValue(*result); return typed ? *typed : defaultValue ? *defaultValue : finalDefaultValue; @@ -58,59 +49,29 @@ public: return defaultValue ? *defaultValue : finalDefaultValue; } - float interpolationFactor(const Range& inputLevels, const float inputValue) const { - return zoomCurve.match( - [](std::nullptr_t) { - assert(false); - return 0.0f; - }, - [&](const expression::Interpolate* z) { - return z->interpolationFactor(Range { inputLevels.min, inputLevels.max }, inputValue); - }, - [&](const expression::Step*) { - return 0.0f; - } - ); + T evaluate(float zoom) const { + return evaluate(expression::EvaluationContext(zoom)); } - Range getCoveringStops(const float lower, const float upper) const { - return zoomCurve.match( - [](std::nullptr_t) { - assert(false); - return Range(0.0f, 0.0f); - }, - [&](auto z) { - return z->getCoveringStops(lower, upper); - } - ); + T evaluate(const GeometryTileFeature& feature, T finalDefaultValue) const { + return evaluate(expression::EvaluationContext(&feature), finalDefaultValue); } - // Return the range obtained by evaluating the function at each of the zoom levels in zoomRange - template - Range evaluate(const Range& zoomRange, const Feature& feature, T finalDefaultValue) { - return Range { - evaluate(zoomRange.min, feature, finalDefaultValue), - evaluate(zoomRange.max, feature, finalDefaultValue) - }; + T evaluate(float zoom, const GeometryTileFeature& feature, T finalDefaultValue) const { + return evaluate(expression::EvaluationContext(zoom, &feature), finalDefaultValue); } std::vector> possibleOutputs() const { return expression::fromExpressionValues(expression->possibleOutputs()); } - const expression::Expression& getExpression() const { return *expression; } - - bool useIntegerZoom = false; - friend bool operator==(const PropertyExpression& lhs, const PropertyExpression& rhs) { return *lhs.expression == *rhs.expression; } private: - std::shared_ptr expression; optional defaultValue; - variant zoomCurve; }; } // namespace style diff --git a/src/core-files.json b/src/core-files.json index 42a30a3ea8..f27bd268bd 100644 --- a/src/core-files.json +++ b/src/core-files.json @@ -223,6 +223,7 @@ "src/mbgl/style/light.cpp", "src/mbgl/style/light_impl.cpp", "src/mbgl/style/parser.cpp", + "src/mbgl/style/property_expression.cpp", "src/mbgl/style/source.cpp", "src/mbgl/style/source_impl.cpp", "src/mbgl/style/sources/custom_geometry_source.cpp", diff --git a/src/mbgl/style/conversion/function.cpp b/src/mbgl/style/conversion/function.cpp index 5877d0eb7c..79ad2fc7d8 100644 --- a/src/mbgl/style/conversion/function.cpp +++ b/src/mbgl/style/conversion/function.cpp @@ -41,7 +41,7 @@ bool hasTokens(const std::string& source) { std::unique_ptr convertTokenStringToFormatExpression(const std::string& source) { auto textExpression = convertTokenStringToExpression(source); std::vector sections; - sections.emplace_back(std::move(textExpression), nullopt, nullopt); + sections.emplace_back(std::move(textExpression), nullopt, nullopt, nullopt); return std::make_unique(sections); } diff --git a/src/mbgl/style/expression/compound_expression.cpp b/src/mbgl/style/expression/compound_expression.cpp index cc1d58025b..27dfe6c151 100644 --- a/src/mbgl/style/expression/compound_expression.cpp +++ b/src/mbgl/style/expression/compound_expression.cpp @@ -662,6 +662,16 @@ const auto& errorCompoundExpression() { return signature; } +const auto& textSectionCompoundExpression() { + static auto signature = detail::makeSignature("text-section", [](const EvaluationContext& params) -> Result { + if (!params.formattedSection) { + return EvaluationError {"Formatted section is unavailable in the current evaluation context."}; + } + return *params.formattedSection; + }); + return signature; +} + // Legacy Filters const auto& filterEqualsCompoundExpression() { static auto signature = detail::makeSignature("filter-==", [](const EvaluationContext& params, const std::string& key, const Value &lhs) -> Result { @@ -907,6 +917,7 @@ MAPBOX_ETERNAL_CONSTEXPR const auto compoundExpressionRegistry = mapbox::eternal { "concat", concatCompoundExpression }, { "resolved-locale", resolvedLocaleCompoundExpression }, { "error", errorCompoundExpression }, + { "text-section", textSectionCompoundExpression }, // Legacy Filters { "filter-==", filterEqualsCompoundExpression }, { "filter-id-==", filterIdEqualsCompoundExpression }, diff --git a/src/mbgl/style/expression/dsl.cpp b/src/mbgl/style/expression/dsl.cpp index f5ff83a9e7..f999c90b07 100644 --- a/src/mbgl/style/expression/dsl.cpp +++ b/src/mbgl/style/expression/dsl.cpp @@ -189,13 +189,17 @@ std::unique_ptr concat(std::vector> inpu std::unique_ptr format(const char* value) { return std::make_unique(Formatted(value)); } - + std::unique_ptr format(std::unique_ptr input) { std::vector sections; - sections.emplace_back(std::move(input), nullopt, nullopt); + sections.emplace_back(std::move(input), nullopt, nullopt, nullopt); return std::make_unique(sections); } - + +std::unique_ptr textSection() { + return compound("text-section"); +} + } // namespace dsl } // namespace expression } // namespace style diff --git a/src/mbgl/style/expression/format_expression.cpp b/src/mbgl/style/expression/format_expression.cpp index 144df4b160..57204313b4 100644 --- a/src/mbgl/style/expression/format_expression.cpp +++ b/src/mbgl/style/expression/format_expression.cpp @@ -1,8 +1,6 @@ #include #include -#include -#include -#include +#include namespace mbgl { namespace style { @@ -10,15 +8,21 @@ namespace expression { FormatExpressionSection::FormatExpressionSection(std::unique_ptr text_, optional> fontScale_, - optional> textFont_) + optional> textFont_, + optional> sectionID_) : text(std::move(text_)) { if (fontScale_) { fontScale = std::shared_ptr(std::move(*fontScale_)); } + if (textFont_) { textFont = std::shared_ptr(std::move(*textFont_)); } + + if (sectionID_) { + sectionID = std::shared_ptr(std::move(*sectionID_)); + } } FormatExpression::FormatExpression(std::vector sections_) @@ -53,7 +57,7 @@ ParseResult FormatExpression::parse(const Convertible& value, ParsingContext& ct return ParseResult(); } - const optional fontScaleOption = objectMember(options, "font-scale"); + const optional fontScaleOption = objectMember(options, kFormattedSectionFontScale); ParseResult fontScale; if (fontScaleOption) { fontScale = ctx.parse(*fontScaleOption, 1, {type::Number}); @@ -62,7 +66,7 @@ ParseResult FormatExpression::parse(const Convertible& value, ParsingContext& ct } } - const optional textFontOption = objectMember(options, "text-font"); + const optional textFontOption = objectMember(options, kFormattedSectionTextFont); ParseResult textFont; if (textFontOption) { textFont = ctx.parse(*textFontOption, 1, {type::Array(type::String)}); @@ -70,7 +74,20 @@ ParseResult FormatExpression::parse(const Convertible& value, ParsingContext& ct return ParseResult(); } } - sections.emplace_back(std::move(*text), std::move(fontScale), std::move(textFont)); + + const optional sectionIDOption = objectMember(options, kFormattedSectionID); + ParseResult sectionID; + if (sectionIDOption) { + sectionID = ctx.parse(*sectionIDOption, 1, {type::Value}); + if (!sectionID) { + return ParseResult(); + } + } + + sections.emplace_back(std::move(*text), + std::move(fontScale), + std::move(textFont), + std::move(sectionID)); } return ParseResult(std::make_unique(std::move(sections))); @@ -85,6 +102,9 @@ void FormatExpression::eachChild(const std::function& f if (section.textFont) { fn(**section.textFont); } + if (section.sectionID) { + fn(**section.sectionID); + } } } @@ -108,6 +128,10 @@ bool FormatExpression::operator==(const Expression& e) const { (!lhsSection.textFont && rhsSection.textFont)) { return false; } + if ((lhsSection.sectionID && (!rhsSection.sectionID || **lhsSection.sectionID != **rhsSection.sectionID)) || + (!lhsSection.sectionID && rhsSection.sectionID)) { + return false; + } } return true; } @@ -115,15 +139,18 @@ bool FormatExpression::operator==(const Expression& e) const { } mbgl::Value FormatExpression::serialize() const { - std::vector serialized{{ std::string("format") }}; + std::vector serialized{{ getOperator() }}; for (const auto& section : sections) { serialized.push_back(section.text->serialize()); std::unordered_map options; if (section.fontScale) { - options.emplace("font-scale", (*section.fontScale)->serialize()); + options.emplace(kFormattedSectionFontScale, (*section.fontScale)->serialize()); } if (section.textFont) { - options.emplace("text-font", (*section.textFont)->serialize()); + options.emplace(kFormattedSectionTextFont, (*section.textFont)->serialize()); + } + if (section.sectionID) { + options.emplace(kFormattedSectionID, (*section.sectionID)->serialize()); } serialized.push_back(options); } @@ -164,7 +191,20 @@ EvaluationResult FormatExpression::evaluate(const EvaluationContext& params) con } evaluatedTextFont = *textFontValue; } - evaluatedSections.emplace_back(*evaluatedText, evaluatedFontScale, evaluatedTextFont); + + optional evaluatedID; + if (section.sectionID) { + auto sectionIDResult = (*section.sectionID)->evaluate(params); + if (!sectionIDResult) { + return sectionIDResult.error(); + } + + evaluatedID = toFormattedSectionID(*sectionIDResult); + if (!evaluatedID) { + return EvaluationError { "Format section id option must evaluate to string or number" }; + } + } + evaluatedSections.emplace_back(*evaluatedText, evaluatedFontScale, evaluatedTextFont, evaluatedID); } return Formatted(evaluatedSections); } diff --git a/src/mbgl/style/expression/formatted.cpp b/src/mbgl/style/expression/formatted.cpp index 8232d0c698..fef834b20c 100644 --- a/src/mbgl/style/expression/formatted.cpp +++ b/src/mbgl/style/expression/formatted.cpp @@ -1,18 +1,15 @@ #include #include -#include -#include -#include -#include -#include -#include -#include +#include namespace mbgl { namespace style { - namespace expression { +const char* const kFormattedSectionFontScale = "font-scale"; +const char* const kFormattedSectionTextFont = "text-font"; +const char* const kFormattedSectionID = "id"; + bool Formatted::operator==(const Formatted& other) const { if (other.sections.size() != sections.size()) { return false; @@ -22,14 +19,14 @@ bool Formatted::operator==(const Formatted& other) const { const auto& otherSection = other.sections.at(i); if (thisSection.text != otherSection.text || thisSection.fontScale != otherSection.fontScale || - thisSection.fontStack != otherSection.fontStack) { + thisSection.fontStack != otherSection.fontStack || + thisSection.id != otherSection.id) { return false; } } return true; } - - + std::string Formatted::toString() const { std::string result; for (const auto& section : sections) { @@ -37,7 +34,7 @@ std::string Formatted::toString() const { } return result; } - + } // namespace expression namespace conversion { @@ -65,6 +62,7 @@ optional Converter::operator()(const Convertible& value, E optional fontScale; optional textFont; + optional id; if (sectionLength > 1) { Convertible sectionParams = arrayMember(section, 1); if (!isObject(sectionParams)) { @@ -72,12 +70,12 @@ optional Converter::operator()(const Convertible& value, E return nullopt; } - optional fontScaleMember = objectMember(sectionParams, "font-scale"); + optional fontScaleMember = objectMember(sectionParams, kFormattedSectionFontScale); if (fontScaleMember) { fontScale = toDouble(*fontScaleMember); } - optional textFontMember = objectMember(sectionParams, "text-font"); + optional textFontMember = objectMember(sectionParams, kFormattedSectionTextFont); if (textFontMember) { if (isArray(*textFontMember)) { std::vector fontsVector; @@ -96,9 +94,23 @@ optional Converter::operator()(const Convertible& value, E return nullopt; } } + + optional sectionIDMember = objectMember(sectionParams, kFormattedSectionID); + if (sectionIDMember) { + auto result = toValue(*sectionIDMember); + if (!result) { + return nullopt; + } + + id = toFormattedSectionID(*result); + if (!id) { + error.message = "Section id has to be a string or a number."; + return nullopt; + } + } } - sections.push_back(FormattedSection(*sectionText, fontScale, textFont)); + sections.push_back(FormattedSection(*sectionText, fontScale, textFont, id)); } return Formatted(sections); } else if (optional result = toString(value)) { diff --git a/src/mbgl/style/expression/is_constant.cpp b/src/mbgl/style/expression/is_constant.cpp index 3b20f49a86..8ac362373c 100644 --- a/src/mbgl/style/expression/is_constant.cpp +++ b/src/mbgl/style/expression/is_constant.cpp @@ -17,15 +17,16 @@ bool isFeatureConstant(const Expression& expression) { return false; } else if (name == "has" && parameterCount && *parameterCount == 1) { return false; - } else if (0 == name.rfind(filter, 0)) { - // Legacy filters begin with "filter-" and are never constant. - return false; } else if ( name == "properties" || name == "geometry-type" || - name == "id" + name == "id" || + name == "text-section" ) { return false; + } else if (0 == name.rfind(filter, 0)) { + // Legacy filters begin with "filter-" and are never constant. + return false; } } diff --git a/src/mbgl/style/property_expression.cpp b/src/mbgl/style/property_expression.cpp new file mode 100644 index 0000000000..6ba0416ad3 --- /dev/null +++ b/src/mbgl/style/property_expression.cpp @@ -0,0 +1,68 @@ +#include + +namespace mbgl { +namespace style { + +PropertyExpressionBase::PropertyExpressionBase(std::unique_ptr expression_) + : expression(std::move(expression_)), + zoomCurve(expression::findZoomCurveChecked(expression.get())) { + isZoomConstant_ = expression::isZoomConstant(*expression); + isFeatureConstant_ = expression::isFeatureConstant(*expression); +} + +bool PropertyExpressionBase::isZoomConstant() const noexcept { + return isZoomConstant_; +} + +bool PropertyExpressionBase::isFeatureConstant() const noexcept { + return isFeatureConstant_; +} + +bool PropertyExpressionBase::canEvaluateWith(const expression::EvaluationContext& context) const noexcept { + if (context.zoom) { + if (context.feature != nullptr) { + return !isFeatureConstant(); + } + return !isZoomConstant() && isFeatureConstant(); + } + + if (context.feature != nullptr) { + return isZoomConstant() && !isFeatureConstant(); + } + + return true; +} + +float PropertyExpressionBase::interpolationFactor(const Range& inputLevels, const float inputValue) const noexcept { + return zoomCurve.match( + [](std::nullptr_t) { + assert(false); + return 0.0f; + }, + [&](const expression::Interpolate* z) { + return z->interpolationFactor(Range { inputLevels.min, inputLevels.max }, inputValue); + }, + [&](const expression::Step*) { + return 0.0f; + } + ); +} + +Range PropertyExpressionBase::getCoveringStops(const float lower, const float upper) const noexcept { + return zoomCurve.match( + [](std::nullptr_t) { + assert(false); + return Range(0.0f, 0.0f); + }, + [&](auto z) { + return z->getCoveringStops(lower, upper); + } + ); +} + +const expression::Expression& PropertyExpressionBase::getExpression() const noexcept { + return *expression; +} + +} // namespace style +} // namespace mbgl -- cgit v1.2.1