diff options
author | Anand Thakker <github@anandthakker.net> | 2017-11-01 22:36:24 -0400 |
---|---|---|
committer | Anand Thakker <github@anandthakker.net> | 2017-11-02 08:44:06 -0400 |
commit | ac86e41f95b7578eb3135b17f15358e27b97e7b2 (patch) | |
tree | 74514d1de158b6d33df9821635422e26e3463dd6 | |
parent | fb1ecbfa1381c73d48dd766580c00a8c96e653e0 (diff) | |
download | qtlocation-mapboxgl-ac86e41f95b7578eb3135b17f15358e27b97e7b2.tar.gz |
Split "curve" into "step" and "interpolate" expressions
20 files changed, 440 insertions, 225 deletions
diff --git a/cmake/core-files.cmake b/cmake/core-files.cmake index 8d30de0a17..9a9236d363 100644 --- a/cmake/core-files.cmake +++ b/cmake/core-files.cmake @@ -404,14 +404,17 @@ set(MBGL_CORE_FILES include/mbgl/style/expression/coalesce.hpp include/mbgl/style/expression/coercion.hpp include/mbgl/style/expression/compound_expression.hpp - include/mbgl/style/expression/curve.hpp include/mbgl/style/expression/expression.hpp + include/mbgl/style/expression/find_zoom_curve.hpp + include/mbgl/style/expression/get_covering_stops.hpp + include/mbgl/style/expression/interpolate.hpp include/mbgl/style/expression/is_constant.hpp include/mbgl/style/expression/is_expression.hpp include/mbgl/style/expression/let.hpp include/mbgl/style/expression/literal.hpp include/mbgl/style/expression/match.hpp include/mbgl/style/expression/parsing_context.hpp + include/mbgl/style/expression/step.hpp include/mbgl/style/expression/type.hpp include/mbgl/style/expression/value.hpp src/mbgl/style/expression/array_assertion.cpp @@ -423,13 +426,15 @@ set(MBGL_CORE_FILES src/mbgl/style/expression/coalesce.cpp src/mbgl/style/expression/coercion.cpp src/mbgl/style/expression/compound_expression.cpp - src/mbgl/style/expression/curve.cpp + src/mbgl/style/expression/get_covering_stops.cpp + src/mbgl/style/expression/interpolate.cpp src/mbgl/style/expression/is_constant.cpp src/mbgl/style/expression/is_expression.cpp src/mbgl/style/expression/let.cpp src/mbgl/style/expression/literal.cpp src/mbgl/style/expression/match.cpp src/mbgl/style/expression/parsing_context.cpp + src/mbgl/style/expression/step.cpp src/mbgl/style/expression/util.cpp src/mbgl/style/expression/util.hpp src/mbgl/style/expression/value.cpp diff --git a/include/mbgl/style/conversion/data_driven_property_value.hpp b/include/mbgl/style/conversion/data_driven_property_value.hpp index 2fc98494ac..7a0ac56ff4 100644 --- a/include/mbgl/style/conversion/data_driven_property_value.hpp +++ b/include/mbgl/style/conversion/data_driven_property_value.hpp @@ -6,8 +6,8 @@ #include <mbgl/style/conversion/function.hpp> #include <mbgl/style/conversion/expression.hpp> #include <mbgl/style/expression/is_expression.hpp> -#include <mbgl/style/expression/curve.hpp> #include <mbgl/style/expression/is_constant.hpp> +#include <mbgl/style/expression/find_zoom_curve.hpp> #include <unordered_set> @@ -34,8 +34,8 @@ struct Converter<DataDrivenPropertyValue<T>> { bool zoomConstant = isZoomConstant(expression->get()); - if (!zoomConstant && !CameraFunction<T>::Curve::findZoomCurve(expression->get())) { - error = { R"("zoom" expression may only be used as input to a top-level "curve" expression.)" }; + if (!zoomConstant && !findZoomCurve<typename CameraFunction<T>::ExpressionType>(expression->get())) { + error = { R"("zoom" expression may only be used as input to a top-level "step" or "interpolate" expression.)" }; return {}; } diff --git a/include/mbgl/style/expression/find_zoom_curve.hpp b/include/mbgl/style/expression/find_zoom_curve.hpp new file mode 100644 index 0000000000..81a4951bf5 --- /dev/null +++ b/include/mbgl/style/expression/find_zoom_curve.hpp @@ -0,0 +1,50 @@ +#pragma once + +#include <mbgl/style/expression/step.hpp> +#include <mbgl/style/expression/compound_expression.hpp> +#include <mbgl/style/expression/let.hpp> +#include <mbgl/style/expression/coalesce.hpp> + +#include <mbgl/util/variant.hpp> +#include <mbgl/util/optional.hpp> + +namespace mbgl { +namespace style { +namespace expression { + +template <typename T> +optional<variant<Interpolate<T>*, Step*>> findZoomCurve(expression::Expression* e) { + if (auto curve = dynamic_cast<Interpolate<T>*>(e)) { + auto z = dynamic_cast<CompoundExpressionBase*>(curve->getInput().get()); + if (z && z->getName() == "zoom") { + return {curve}; + } else { + return optional<variant<Interpolate<T>*, Step*>>(); + } + } else if (auto step = dynamic_cast<Step*>(e)) { + auto z = dynamic_cast<CompoundExpressionBase*>(step->getInput().get()); + if (z && z->getName() == "zoom") { + return {step}; + } else { + return optional<variant<Interpolate<T>*, Step*>>(); + } + } else if (auto let = dynamic_cast<Let*>(e)) { + return findZoomCurve<T>(let->getResult()); + } else if (auto coalesce = dynamic_cast<Coalesce*>(e)) { + std::size_t length = coalesce->getLength(); + for (std::size_t i = 0; i < length; i++) { + optional<variant<Interpolate<T>*, Step*>> childInterpolate = findZoomCurve<T>(coalesce->getChild(i)); + if (!childInterpolate) { + continue; + } else { + return childInterpolate; + } + } + } + + return optional<variant<Interpolate<T>*, Step*>>(); +} + +} // namespace expression +} // namespace style +} // namespace mbgl diff --git a/include/mbgl/style/expression/get_covering_stops.hpp b/include/mbgl/style/expression/get_covering_stops.hpp new file mode 100644 index 0000000000..157aefe7bc --- /dev/null +++ b/include/mbgl/style/expression/get_covering_stops.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include <mbgl/style/expression/expression.hpp> +#include <mbgl/util/range.hpp> +#include <memory> +#include <map> + +namespace mbgl { +namespace style { +namespace expression { + +// Return the smallest range of stops that covers the interval [lower, upper] +Range<float> getCoveringStops(const std::map<double, std::unique_ptr<Expression>>& stops, + const double lower, const double upper); + +} // namespace expression +} // namespace style +} // namespace mbgl diff --git a/include/mbgl/style/expression/curve.hpp b/include/mbgl/style/expression/interpolate.hpp index c1d0eb6ac9..8d11bbcb05 100644 --- a/include/mbgl/style/expression/curve.hpp +++ b/include/mbgl/style/expression/interpolate.hpp @@ -2,9 +2,7 @@ #include <mbgl/style/expression/expression.hpp> #include <mbgl/style/expression/parsing_context.hpp> -#include <mbgl/style/expression/compound_expression.hpp> -#include <mbgl/style/expression/let.hpp> -#include <mbgl/style/expression/coalesce.hpp> +#include <mbgl/style/expression/get_covering_stops.hpp> #include <mbgl/style/conversion.hpp> #include <mbgl/util/interpolate.hpp> @@ -19,13 +17,6 @@ namespace mbgl { namespace style { namespace expression { -class StepInterpolator { -public: - double interpolationFactor(const Range<double>&, const double) const { - return 0; - } - -}; class ExponentialInterpolator { public: @@ -55,20 +46,15 @@ public: }; -ParseResult parseCurve(const mbgl::style::conversion::Convertible& value, ParsingContext& ctx); +ParseResult parseInterpolate(const mbgl::style::conversion::Convertible& value, ParsingContext& ctx); template <typename T> -class Curve : public Expression { +class Interpolate : public Expression { public: - using Interpolator = std::conditional_t< - util::Interpolatable<T>::value, - variant<StepInterpolator, - ExponentialInterpolator, - CubicBezierInterpolator>, - variant<StepInterpolator>>; + using Interpolator = variant<ExponentialInterpolator, CubicBezierInterpolator>; - Curve(const type::Type& type_, + Interpolate(const type::Type& type_, Interpolator interpolator_, std::unique_ptr<Expression> input_, std::map<double, std::unique_ptr<Expression>> stops_ @@ -76,7 +62,9 @@ public: interpolator(std::move(interpolator_)), input(std::move(input_)), stops(std::move(stops_)) - {} + { + static_assert(util::Interpolatable<T>::value, "Interpolate expression requires an interpolatable value type."); + } EvaluationResult evaluate(const EvaluationContext& params) const override { const EvaluationResult evaluatedInput = input->evaluate(params); @@ -123,46 +111,11 @@ public: } } - static optional<Curve*> findZoomCurve(expression::Expression* e) { - if (auto curve = dynamic_cast<Curve*>(e)) { - auto z = dynamic_cast<CompoundExpressionBase*>(curve->input.get()); - if (z && z->getName() == "zoom") { - return {curve}; - } else { - return optional<Curve*>(); - } - } else if (auto let = dynamic_cast<Let*>(e)) { - return findZoomCurve(let->getResult()); - } else if (auto coalesce = dynamic_cast<Coalesce*>(e)) { - std::size_t length = coalesce->getLength(); - for (std::size_t i = 0; i < length; i++) { - optional<Curve*> childCurve = findZoomCurve(coalesce->getChild(i)); - if (!childCurve) { - continue; - } else { - return childCurve; - } - } - } - - return optional<Curve*>(); - } + const std::unique_ptr<Expression>& getInput() const { return input; } // Return the smallest range of stops that covers the interval [lower, upper] Range<float> getCoveringStops(const double lower, const double upper) const { - assert(!stops.empty()); - auto minIt = stops.lower_bound(lower); - auto maxIt = stops.lower_bound(upper); - - // lower_bound yields first element >= lowerZoom, but we want the *last* - // element <= lowerZoom, so if we found a stop > lowerZoom, back up by one. - if (minIt != stops.begin() && minIt != stops.end() && minIt->first > lower) { - minIt--; - } - return Range<float> { - static_cast<float>(minIt == stops.end() ? stops.rbegin()->first : minIt->first), - static_cast<float>(maxIt == stops.end() ? stops.rbegin()->first : maxIt->first) - }; + return ::mbgl::style::expression::getCoveringStops(stops, lower, upper); } double interpolationFactor(const Range<double>& inputLevels, const double inputValue) const { @@ -172,18 +125,7 @@ public: } private: - template <typename OutputType = T> - static EvaluationResult interpolate(const Range<Value>&, const double, - typename std::enable_if<!util::Interpolatable<OutputType>::value>::type* = nullptr) { - // Assume that Curve::evaluate() will always short circuit due to - // interpolationFactor always returning 0. - assert(false); - return Null; - } - - template <typename OutputType = T> - static EvaluationResult interpolate(const Range<Value>& outputs, const double t, - typename std::enable_if<util::Interpolatable<OutputType>::value>::type* = nullptr) { + static EvaluationResult interpolate(const Range<Value>& outputs, const double t) { optional<T> lower = fromExpressionValue<T>(outputs.min); if (!lower) { // TODO - refactor fromExpressionValue to return EvaluationResult<T> so as to @@ -204,7 +146,6 @@ private: return toExpressionValue(result); } - const Interpolator interpolator; const std::unique_ptr<Expression> input; const std::map<double, std::unique_ptr<Expression>> stops; diff --git a/include/mbgl/style/expression/step.hpp b/include/mbgl/style/expression/step.hpp new file mode 100644 index 0000000000..81e1aef4e9 --- /dev/null +++ b/include/mbgl/style/expression/step.hpp @@ -0,0 +1,43 @@ + +#pragma once + +#include <mbgl/style/expression/expression.hpp> +#include <mbgl/style/expression/parsing_context.hpp> +#include <mbgl/style/conversion.hpp> + +#include <mbgl/util/range.hpp> + +#include <memory> +#include <map> + + +namespace mbgl { +namespace style { +namespace expression { + +class Step : public Expression { +public: + Step(const type::Type& type_, + std::unique_ptr<Expression> input_, + std::map<double, std::unique_ptr<Expression>> stops_ + ) : Expression(type_), + input(std::move(input_)), + stops(std::move(stops_)) + {} + + EvaluationResult evaluate(const EvaluationContext& params) const override; + void eachChild(const std::function<void(const Expression*)>& visit) const override; + + const std::unique_ptr<Expression>& getInput() const { return input; } + Range<float> getCoveringStops(const double lower, const double upper) const; + + static ParseResult parse(const mbgl::style::conversion::Convertible& value, ParsingContext& ctx); + +private: + const std::unique_ptr<Expression> input; + const std::map<double, std::unique_ptr<Expression>> stops; +}; + +} // namespace expression +} // namespace style +} // namespace mbgl diff --git a/include/mbgl/style/function/camera_function.hpp b/include/mbgl/style/function/camera_function.hpp index c44002e8c1..721d3083e1 100644 --- a/include/mbgl/style/function/camera_function.hpp +++ b/include/mbgl/style/function/camera_function.hpp @@ -1,7 +1,9 @@ #pragma once #include <mbgl/style/expression/expression.hpp> -#include <mbgl/style/expression/curve.hpp> +#include <mbgl/style/expression/interpolate.hpp> +#include <mbgl/style/expression/step.hpp> +#include <mbgl/style/expression/find_zoom_curve.hpp> #include <mbgl/style/expression/value.hpp> #include <mbgl/style/expression/is_constant.hpp> #include <mbgl/style/function/convert.hpp> @@ -17,7 +19,7 @@ namespace style { template <class T> class CameraFunction { public: - using Curve = expression::Curve<typename expression::ValueConverter<T>::ExpressionType>; + using ExpressionType = typename expression::ValueConverter<T>::ExpressionType; using Stops = std::conditional_t< util::Interpolatable<T>::value, @@ -29,7 +31,7 @@ public: CameraFunction(std::unique_ptr<expression::Expression> expression_) : expression(std::move(expression_)), - zoomCurve(*Curve::findZoomCurve(expression.get())) + zoomCurve(*expression::findZoomCurve<ExpressionType>(expression.get())) { assert(!isZoomConstant(expression.get())); assert(isFeatureConstant(expression.get())); @@ -40,7 +42,7 @@ public: expression(stops.match([&] (const auto& s) { return expression::Convert::toExpression(s); })), - zoomCurve(*Curve::findZoomCurve(expression.get())) + zoomCurve(*expression::findZoomCurve<ExpressionType>(expression.get())) {} T evaluate(float zoom) const { @@ -53,11 +55,18 @@ public: } float interpolationFactor(const Range<float>& inputLevels, const float inputValue) const { - return zoomCurve->interpolationFactor(Range<double> { inputLevels.min, inputLevels.max }, inputValue); + return zoomCurve.match( + [&](expression::Interpolate<ExpressionType>* z) { + return z->interpolationFactor(Range<double> { inputLevels.min, inputLevels.max }, inputValue); + }, + [&](expression::Step*) { return 0.0f; } + ); } Range<float> getCoveringStops(const float lower, const float upper) const { - return zoomCurve->getCoveringStops(lower, upper); + return zoomCurve.match( + [&](auto z) { return z->getCoveringStops(lower, upper); } + ); } friend bool operator==(const CameraFunction& lhs, @@ -72,7 +81,7 @@ public: private: std::shared_ptr<expression::Expression> expression; - const Curve* zoomCurve; + const variant<expression::Interpolate<ExpressionType>*, expression::Step*> zoomCurve; }; } // namespace style diff --git a/include/mbgl/style/function/composite_function.hpp b/include/mbgl/style/function/composite_function.hpp index ead31294bb..5c91f1d6c9 100644 --- a/include/mbgl/style/function/composite_function.hpp +++ b/include/mbgl/style/function/composite_function.hpp @@ -1,7 +1,9 @@ #pragma once #include <mbgl/style/expression/expression.hpp> -#include <mbgl/style/expression/curve.hpp> +#include <mbgl/style/expression/interpolate.hpp> +#include <mbgl/style/expression/step.hpp> +#include <mbgl/style/expression/find_zoom_curve.hpp> #include <mbgl/style/expression/value.hpp> #include <mbgl/style/expression/is_constant.hpp> #include <mbgl/style/function/convert.hpp> @@ -48,11 +50,11 @@ public: CompositeIntervalStops<T>, CompositeCategoricalStops<T>>>; - using Curve = expression::Curve<typename expression::ValueConverter<T>::ExpressionType>; + using ExpressionType = typename expression::ValueConverter<T>::ExpressionType; CompositeFunction(std::unique_ptr<expression::Expression> expression_) : expression(std::move(expression_)), - zoomCurve(*Curve::findZoomCurve(expression.get())) + zoomCurve(*expression::findZoomCurve<ExpressionType>(expression.get())) { assert(!isZoomConstant(expression.get())); assert(!isFeatureConstant(expression.get())); @@ -65,7 +67,7 @@ public: expression(stops.match([&] (const auto& s) { return expression::Convert::toExpression(property, s); })), - zoomCurve(*Curve::findZoomCurve(expression.get())) + zoomCurve(*expression::findZoomCurve<ExpressionType>(expression.get())) {} // Return the range obtained by evaluating the function at each of the zoom levels in zoomRange @@ -88,11 +90,18 @@ public: } float interpolationFactor(const Range<float>& inputLevels, const float inputValue) const { - return zoomCurve->interpolationFactor(Range<double> { inputLevels.min, inputLevels.max }, inputValue); + return zoomCurve.match( + [&](expression::Interpolate<ExpressionType>* z) { + return z->interpolationFactor(Range<double> { inputLevels.min, inputLevels.max }, inputValue); + }, + [&](expression::Step*) { return 0.0f; } + ); } Range<float> getCoveringStops(const float lower, const float upper) const { - return zoomCurve->getCoveringStops(lower, upper); + return zoomCurve.match( + [&](auto z) { return z->getCoveringStops(lower, upper); } + ); } friend bool operator==(const CompositeFunction& lhs, @@ -108,7 +117,7 @@ public: private: std::shared_ptr<expression::Expression> expression; - const Curve* zoomCurve; + const variant<expression::Interpolate<ExpressionType>*, expression::Step*> zoomCurve; }; } // namespace style diff --git a/include/mbgl/style/function/convert.hpp b/include/mbgl/style/function/convert.hpp index e07b4038ea..82537f5bfa 100644 --- a/include/mbgl/style/function/convert.hpp +++ b/include/mbgl/style/function/convert.hpp @@ -6,10 +6,11 @@ #include <mbgl/style/expression/coalesce.hpp> #include <mbgl/style/expression/compound_expression.hpp> #include <mbgl/style/expression/coercion.hpp> -#include <mbgl/style/expression/curve.hpp> +#include <mbgl/style/expression/interpolate.hpp> #include <mbgl/style/expression/expression.hpp> #include <mbgl/style/expression/literal.hpp> #include <mbgl/style/expression/match.hpp> +#include <mbgl/style/expression/step.hpp> #include <mbgl/style/function/exponential_stops.hpp> #include <mbgl/style/function/interval_stops.hpp> @@ -82,12 +83,12 @@ struct Convert { } template <typename OutputType> - static ParseResult makeCurve(type::Type type, + static ParseResult makeInterpolate(type::Type type, std::unique_ptr<Expression> input, std::map<double, std::unique_ptr<Expression>> convertedStops, - typename Curve<OutputType>::Interpolator interpolator) + typename Interpolate<OutputType>::Interpolator interpolator) { - ParseResult curve = ParseResult(std::make_unique<Curve<OutputType>>( + ParseResult curve = ParseResult(std::make_unique<Interpolate<OutputType>>( std::move(type), std::move(interpolator), std::move(input), @@ -181,7 +182,7 @@ struct Convert { template <typename T> static std::unique_ptr<Expression> toExpression(const ExponentialStops<T>& stops) { - ParseResult e = makeCurve<typename ValueConverter<T>::ExpressionType>( + ParseResult e = makeInterpolate<typename ValueConverter<T>::ExpressionType>( valueTypeToExpressionType<T>(), makeZoom(), convertStops(stops.stops), @@ -193,10 +194,9 @@ struct Convert { template <typename T> static std::unique_ptr<Expression> toExpression(const IntervalStops<T>& stops) { - ParseResult e = makeCurve<typename ValueConverter<T>::ExpressionType>(valueTypeToExpressionType<T>(), - makeZoom(), - convertStops(stops.stops), - StepInterpolator()); + ParseResult e(std::make_unique<Step>(valueTypeToExpressionType<T>(), + makeZoom(), + convertStops(stops.stops))); assert(e); return std::move(*e); } @@ -205,7 +205,7 @@ struct Convert { static std::unique_ptr<Expression> toExpression(const std::string& property, const ExponentialStops<T>& stops) { - ParseResult e = makeCurve<typename ValueConverter<T>::ExpressionType>(valueTypeToExpressionType<T>(), + ParseResult e = makeInterpolate<typename ValueConverter<T>::ExpressionType>(valueTypeToExpressionType<T>(), makeGet(type::Number, property), convertStops(stops.stops), ExponentialInterpolator(stops.base)); @@ -218,10 +218,9 @@ struct Convert { const IntervalStops<T>& stops) { std::unique_ptr<Expression> get = makeGet(type::Number, property); - ParseResult e = makeCurve<typename ValueConverter<T>::ExpressionType>(valueTypeToExpressionType<T>(), - std::move(get), - convertStops(stops.stops), - StepInterpolator()); + ParseResult e(std::make_unique<Step>(valueTypeToExpressionType<T>(), + std::move(get), + convertStops(stops.stops))); assert(e); return std::move(*e); } @@ -235,17 +234,23 @@ struct Convert { return std::move(*expr); } + // interpolatable zoom curve template <typename T> - static typename Curve<std::enable_if_t<util::Interpolatable<T>::value, T>>::Interpolator zoomInterpolator() { - return ExponentialInterpolator(1.0); + static typename std::enable_if_t<util::Interpolatable<T>::value, + ParseResult> makeZoomCurve(std::map<double, std::unique_ptr<Expression>> stops) { + return makeInterpolate<typename ValueConverter<T>::ExpressionType>(valueTypeToExpressionType<T>(), + makeZoom(), + std::move(stops), + ExponentialInterpolator(1.0)); } + // non-interpolatable zoom curve template <typename T> - static typename Curve<std::enable_if_t<!util::Interpolatable<T>::value, T>>::Interpolator zoomInterpolator() { - return StepInterpolator(); + static typename std::enable_if_t<!util::Interpolatable<T>::value, + ParseResult> makeZoomCurve(std::map<double, std::unique_ptr<Expression>> stops) { + return ParseResult(std::make_unique<Step>(valueTypeToExpressionType<T>(), makeZoom(), std::move(stops))); } - template <typename T> static std::unique_ptr<Expression> toExpression(const std::string& property, const CompositeExponentialStops<T>& stops) @@ -253,19 +258,17 @@ struct Convert { std::map<double, std::unique_ptr<Expression>> outerStops; for (const std::pair<float, std::map<float, T>>& stop : stops.stops) { std::unique_ptr<Expression> get = makeGet(type::Number, property); - ParseResult innerCurve = makeCurve<typename ValueConverter<T>::ExpressionType>(valueTypeToExpressionType<T>(), + ParseResult innerInterpolate = makeInterpolate<typename ValueConverter<T>::ExpressionType>(valueTypeToExpressionType<T>(), std::move(get), convertStops(stop.second), ExponentialInterpolator(stops.base)); - assert(innerCurve); - outerStops.emplace(stop.first, std::move(*innerCurve)); + assert(innerInterpolate); + outerStops.emplace(stop.first, std::move(*innerInterpolate)); } - ParseResult outerCurve = makeCurve<typename ValueConverter<T>::ExpressionType>(valueTypeToExpressionType<T>(), - makeZoom(), - std::move(outerStops), - zoomInterpolator<T>()); - assert(outerCurve); - return std::move(*outerCurve); + + ParseResult zoomCurve = makeZoomCurve<T>(std::move(outerStops)); + assert(zoomCurve); + return std::move(*zoomCurve); } template <typename T> @@ -275,19 +278,16 @@ struct Convert { std::map<double, std::unique_ptr<Expression>> outerStops; for (const std::pair<float, std::map<float, T>>& stop : stops.stops) { std::unique_ptr<Expression> get = makeGet(type::Number, property); - ParseResult innerCurve = makeCurve<typename ValueConverter<T>::ExpressionType>(valueTypeToExpressionType<T>(), - std::move(get), - convertStops(stop.second), - StepInterpolator()); - assert(innerCurve); - outerStops.emplace(stop.first, std::move(*innerCurve)); + ParseResult innerInterpolate(std::make_unique<Step>(valueTypeToExpressionType<T>(), + std::move(get), + convertStops(stop.second))); + assert(innerInterpolate); + outerStops.emplace(stop.first, std::move(*innerInterpolate)); } - ParseResult outerCurve = makeCurve<typename ValueConverter<T>::ExpressionType>(valueTypeToExpressionType<T>(), - makeZoom(), - std::move(outerStops), - zoomInterpolator<T>()); - assert(outerCurve); - return std::move(*outerCurve); + + ParseResult zoomCurve = makeZoomCurve<T>(std::move(outerStops)); + assert(zoomCurve); + return std::move(*zoomCurve); } template <typename T> @@ -296,24 +296,20 @@ struct Convert { { std::map<double, std::unique_ptr<Expression>> outerStops; for (const std::pair<float, std::map<CategoricalValue, T>>& stop : stops.stops) { - ParseResult innerCurve = fromCategoricalStops(stop.second, property); - assert(innerCurve); - outerStops.emplace(stop.first, std::move(*innerCurve)); + ParseResult innerInterpolate = fromCategoricalStops(stop.second, property); + assert(innerInterpolate); + outerStops.emplace(stop.first, std::move(*innerInterpolate)); } - ParseResult outerCurve = makeCurve<typename ValueConverter<T>::ExpressionType>(valueTypeToExpressionType<T>(), - makeZoom(), - std::move(outerStops), - zoomInterpolator<T>()); - assert(outerCurve); - return std::move(*outerCurve); + + ParseResult zoomCurve = makeZoomCurve<T>(std::move(outerStops)); + assert(zoomCurve); + return std::move(*zoomCurve); } - template <typename T> - static std::unique_ptr<Expression> toExpression(const std::string& property, - const IdentityStops<T>&) + static std::unique_ptr<Expression> fromIdentityFunction(type::Type type, const std::string& property) { - std::unique_ptr<Expression> input = valueTypeToExpressionType<T>().match( + std::unique_ptr<Expression> input = type.match( [&] (const type::StringType&) { return makeGet(type::String, property); }, diff --git a/include/mbgl/style/function/source_function.hpp b/include/mbgl/style/function/source_function.hpp index 6b3dfb449e..40e0de737e 100644 --- a/include/mbgl/style/function/source_function.hpp +++ b/include/mbgl/style/function/source_function.hpp @@ -40,8 +40,10 @@ public: : property(std::move(property_)), stops(std::move(stops_)), defaultValue(std::move(defaultValue_)), - expression(stops.match([&] (const auto& s) { - return expression::Convert::toExpression(property, s); + expression(stops.match([&] (const IdentityStops<T>&) { + return expression::Convert::fromIdentityFunction(expression::valueTypeToExpressionType<T>(), property); + }, [&] (const auto& s) { + return expression::Convert::toExpression(property, s); })) {} diff --git a/mapbox-gl-js b/mapbox-gl-js -Subproject e505df99a4559c6c5109247026f6b02ab203767 +Subproject 575e42d332fa26b9f7574047e3c67a44bf5ca87 diff --git a/platform/node/test/expression.test.js b/platform/node/test/expression.test.js index 6ffea28d40..aac039ce18 100644 --- a/platform/node/test/expression.test.js +++ b/platform/node/test/expression.test.js @@ -2,6 +2,7 @@ var suite = require('../../../mapbox-gl-js/test/integration').expression; var mbgl = require('../index'); +var ignores = require('./ignores.json'); var tests; @@ -29,7 +30,7 @@ function getExpectedType(spec) { return typeof spec.type === 'string' ? {kind: spec.type} : null; } -suite.run('native', {tests: tests}, (fixture) => { +suite.run('native', {ignores: ignores, tests: tests}, (fixture) => { const compiled = {}; const result = { compiled diff --git a/platform/node/test/ignores.json b/platform/node/test/ignores.json index ef0568b369..e1a68be449 100644 --- a/platform/node/test/ignores.json +++ b/platform/node/test/ignores.json @@ -1,4 +1,6 @@ { + "expression-tests/curve/step": "https://github.com/mapbox/mapbox-gl-js/issues/5580", + "expression-tests/curve/interpolate": "https://github.com/mapbox/mapbox-gl-js/issues/5580", "query-tests/circle-stroke-width/inside": "https://github.com/mapbox/mapbox-gl-native/issues/10307", "query-tests/geometry/multilinestring": "needs investigation", "query-tests/geometry/multipolygon": "needs investigation", diff --git a/src/mbgl/style/expression/get_covering_stops.cpp b/src/mbgl/style/expression/get_covering_stops.cpp new file mode 100644 index 0000000000..c9f87d93ac --- /dev/null +++ b/src/mbgl/style/expression/get_covering_stops.cpp @@ -0,0 +1,26 @@ +#include <mbgl/style/expression/get_covering_stops.hpp> + +namespace mbgl { +namespace style { +namespace expression { + +Range<float> getCoveringStops(const std::map<double, std::unique_ptr<Expression>>& stops, + const double lower, const double upper) { + assert(!stops.empty()); + auto minIt = stops.lower_bound(lower); + auto maxIt = stops.lower_bound(upper); + + // lower_bound yields first element >= lowerZoom, but we want the *last* + // element <= lowerZoom, so if we found a stop > lowerZoom, back up by one. + if (minIt != stops.begin() && minIt != stops.end() && minIt->first > lower) { + minIt--; + } + return Range<float> { + static_cast<float>(minIt == stops.end() ? stops.rbegin()->first : minIt->first), + static_cast<float>(maxIt == stops.end() ? stops.rbegin()->first : maxIt->first) + }; +} + +} // namespace expression +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/expression/curve.cpp b/src/mbgl/style/expression/interpolate.cpp index d786a147aa..c94e3ff64e 100644 --- a/src/mbgl/style/expression/curve.cpp +++ b/src/mbgl/style/expression/interpolate.cpp @@ -1,42 +1,36 @@ -#include <mbgl/style/expression/curve.hpp> +#include <mbgl/style/expression/interpolate.hpp> namespace mbgl { namespace style { namespace expression { -using Interpolator = variant<StepInterpolator, - ExponentialInterpolator, +using Interpolator = variant<ExponentialInterpolator, CubicBezierInterpolator>; using namespace mbgl::style::conversion; -ParseResult parseCurve(const Convertible& value, ParsingContext& ctx) { +ParseResult parseInterpolate(const Convertible& value, ParsingContext& ctx) { assert(isArray(value)); auto length = arrayLength(value); - // first parse interpolation, because further validation of the input depends upon - // whether or not this is a step curve if (length < 2) { ctx.error("Expected an interpolation type expression."); return ParseResult(); } + const Convertible& interp = arrayMember(value, 1); if (!isArray(interp) || arrayLength(interp) == 0) { ctx.error("Expected an interpolation type expression."); return ParseResult(); } - Interpolator interpolator; - bool isStep = false; + optional<Interpolator> interpolator; const optional<std::string> interpName = toString(arrayMember(interp, 0)); - if (interpName && *interpName == "step") { - interpolator = StepInterpolator(); - isStep = true; - } else if (interpName && *interpName == "linear") { - interpolator = ExponentialInterpolator(1.0); + if (interpName && *interpName == "linear") { + interpolator = {ExponentialInterpolator(1.0)}; } else if (interpName && *interpName == "exponential") { optional<double> base; if (arrayLength(interp) == 2) { @@ -46,7 +40,7 @@ ParseResult parseCurve(const Convertible& value, ParsingContext& ctx) { ctx.error("Exponential interpolation requires a numeric base.", 1, 1); return ParseResult(); } - interpolator = ExponentialInterpolator(*base); + interpolator = {ExponentialInterpolator(*base)}; } else if (interpName && *interpName == "cubic-bezier") { optional<double> x1; optional<double> y1; @@ -69,31 +63,31 @@ ParseResult parseCurve(const Convertible& value, ParsingContext& ctx) { return ParseResult(); } - interpolator = CubicBezierInterpolator(*x1, *y1, *x2, *y2); - } else { + interpolator = {CubicBezierInterpolator(*x1, *y1, *x2, *y2)}; + } + + if (!interpolator) { ctx.error("Unknown interpolation type " + (interpName ? *interpName : ""), 1, 0); return ParseResult(); } - std::size_t minArgs = isStep ? 5 : 4; + std::size_t minArgs = 4; if (length - 1 < minArgs) { - ctx.error("Expected at least " + std::to_string(minArgs) + " arguments, but found only " + std::to_string(length - 1) + "."); + ctx.error("Expected at least 4 arguments, but found only " + std::to_string(length - 1) + "."); return ParseResult(); } - bool parity = minArgs % 2; - // [curve, interp, input, 2 * (n pairs)...] - if ((length - 1) % 2 != parity) { - ctx.error("Expected an " + std::string(parity ? "odd" : "even") + " number of arguments."); + // [interpolation, interp_type, input, 2 * (n pairs)...] + if ((length - 1) % 2 != 0) { + ctx.error("Expected an even number of arguments."); return ParseResult(); } - ParseResult input = ctx.parse(arrayMember(value, 2), 2, {type::Number}); if (!input) { return input; } - + std::map<double, std::unique_ptr<Expression>> stops; optional<type::Type> outputType; if (ctx.getExpected() && *ctx.getExpected() != type::Value) { @@ -102,21 +96,7 @@ ParseResult parseCurve(const Convertible& value, ParsingContext& ctx) { double previous = - std::numeric_limits<double>::infinity(); - // If this is a step curve, the definition begins with an output value rather - // than an input level, so consume that output value before proceeding into the - // "stops" loop below. - if (isStep) { - auto output = ctx.parse(arrayMember(value, 3), 3, outputType); - if (!output) { - return ParseResult(); - } - if (!outputType) { - outputType = (*output)->getType(); - } - stops.emplace(-std::numeric_limits<double>::infinity(), std::move(*output)); - } - - for (std::size_t i = isStep ? 4 : 3; i + 1 < length; i += 2) { + for (std::size_t i = 3; i + 1 < length; i += 2) { const optional<mbgl::Value> labelValue = toValue(arrayMember(value, i)); optional<double> label; optional<std::string> labelError; @@ -148,14 +128,14 @@ ParseResult parseCurve(const Convertible& value, ParsingContext& ctx) { } if (!label) { ctx.error(labelError ? *labelError : - R"(Input/output pairs for "curve" expressions must be defined using literal numeric values (not computed expressions) for the input values.)", + R"(Input/output pairs for "interpolate" expressions must be defined using literal numeric values (not computed expressions) for the input values.)", i); return ParseResult(); } if (*label < previous) { ctx.error( - R"(Input/output pairs for "curve" expressions must be arranged with input values in strictly ascending order.)", + R"(Input/output pairs for "interpolate" expressions must be arranged with input values in strictly ascending order.)", i ); return ParseResult(); @@ -176,7 +156,6 @@ ParseResult parseCurve(const Convertible& value, ParsingContext& ctx) { assert(outputType); if ( - !interpolator.template is<StepInterpolator>() && *outputType != type::Number && *outputType != type::Color && !( @@ -186,58 +165,43 @@ ParseResult parseCurve(const Convertible& value, ParsingContext& ctx) { ) ) { - ctx.error("Type " + toString(*outputType) + - " is not interpolatable, and thus cannot be used as a " + - *interpName + " curve's output type."); + ctx.error("Type " + toString(*outputType) + " is not interpolatable."); return ParseResult(); } return outputType->match( [&](const type::NumberType&) -> ParseResult { - return interpolator.match([&](const auto& interpolator_) { - return ParseResult(std::make_unique<Curve<double>>( + return interpolator->match([&](const auto& interpolator_) { + return ParseResult(std::make_unique<Interpolate<double>>( *outputType, interpolator_, std::move(*input), std::move(stops) )); }); }, [&](const type::ColorType&) -> ParseResult { - return interpolator.match([&](const auto& interpolator_) { - return ParseResult(std::make_unique<Curve<Color>>( + return interpolator->match([&](const auto& interpolator_) { + return ParseResult(std::make_unique<Interpolate<Color>>( *outputType, interpolator_, std::move(*input), std::move(stops) )); }); }, [&](const type::Array& arrayType) -> ParseResult { - return interpolator.match( - [&](const StepInterpolator& stepInterpolator) { - return ParseResult(std::make_unique<Curve<std::vector<Value>>>( - *outputType, stepInterpolator, std::move(*input), std::move(stops) - )); - }, + return interpolator->match( [&](const auto& continuousInterpolator) { if (arrayType.itemType != type::Number || !arrayType.N) { assert(false); // interpolability already checked above. return ParseResult(); } - return ParseResult(std::make_unique<Curve<std::vector<Value>>>( + return ParseResult(std::make_unique<Interpolate<std::vector<Value>>>( *outputType, continuousInterpolator, std::move(*input), std::move(stops) )); } ); }, [&](const auto&) { - // Null, Boolean, String, Object, Value output types only support step interpolation - return interpolator.match( - [&](const StepInterpolator& stepInterpolator) { - return ParseResult(std::make_unique<Curve<double>>( - *outputType, stepInterpolator, std::move(*input), std::move(stops) - )); - }, - [&](const auto&) { - assert(false); // interpolability already checked above. - return ParseResult(); - } - ); + // unreachable: Null, Boolean, String, Object, Value output types + // are not interpolatable, and interpolability was already checked above + assert(false); + return ParseResult(); } ); } diff --git a/src/mbgl/style/expression/parsing_context.cpp b/src/mbgl/style/expression/parsing_context.cpp index 18a88b8199..46d5942c70 100644 --- a/src/mbgl/style/expression/parsing_context.cpp +++ b/src/mbgl/style/expression/parsing_context.cpp @@ -13,10 +13,11 @@ #include <mbgl/style/expression/coalesce.hpp> #include <mbgl/style/expression/coercion.hpp> #include <mbgl/style/expression/compound_expression.hpp> -#include <mbgl/style/expression/curve.hpp> +#include <mbgl/style/expression/interpolate.hpp> #include <mbgl/style/expression/let.hpp> #include <mbgl/style/expression/literal.hpp> #include <mbgl/style/expression/match.hpp> +#include <mbgl/style/expression/step.hpp> #include <mbgl/style/conversion/get_json_type.hpp> @@ -77,12 +78,13 @@ const ExpressionRegistry& getExpressionRegistry() { {"boolean", Assertion::parse}, {"case", Case::parse}, {"coalesce", Coalesce::parse}, - {"curve", parseCurve}, + {"interpolate", parseInterpolate}, {"let", Let::parse}, {"literal", Literal::parse}, {"match", parseMatch}, {"number", Assertion::parse}, {"object", Assertion::parse}, + {"step", Step::parse}, {"string", Assertion::parse}, {"to-color", Coercion::parse}, {"to-number", Coercion::parse}, diff --git a/src/mbgl/style/expression/step.cpp b/src/mbgl/style/expression/step.cpp new file mode 100644 index 0000000000..f180f5c587 --- /dev/null +++ b/src/mbgl/style/expression/step.cpp @@ -0,0 +1,147 @@ +#include <mbgl/style/expression/step.hpp> +#include <mbgl/style/expression/get_covering_stops.hpp> + + +namespace mbgl { +namespace style { +namespace expression { + +EvaluationResult Step::evaluate(const EvaluationContext& params) const { + const EvaluationResult evaluatedInput = input->evaluate(params); + if (!evaluatedInput) { return evaluatedInput.error(); } + float x = *fromExpressionValue<float>(*evaluatedInput); + + if (stops.empty()) { + return EvaluationError { "No stops in step curve." }; + } + + auto it = stops.upper_bound(x); + if (it == stops.end()) { + return stops.rbegin()->second->evaluate(params); + } else if (it == stops.begin()) { + return stops.begin()->second->evaluate(params); + } else { + return std::prev(it)->second->evaluate(params); + } +} + +void Step::eachChild(const std::function<void(const Expression*)>& visit) const { + visit(input.get()); + for (auto it = stops.begin(); it != stops.end(); it++) { + visit(it->second.get()); + } +} + +Range<float> Step::getCoveringStops(const double lower, const double upper) const { + return ::mbgl::style::expression::getCoveringStops(stops, lower, upper); +} + + +ParseResult Step::parse(const mbgl::style::conversion::Convertible& value, ParsingContext& ctx) { + assert(isArray(value)); + + auto length = arrayLength(value); + + if (length - 1 < 4) { + ctx.error("Expected at least 4 arguments, but found only " + std::to_string(length - 1) + "."); + return ParseResult(); + } + + // [step, input, first_output_value, 2 * (n pairs)...] + if ((length - 1) % 2 != 0) { + ctx.error("Expected an even number of arguments."); + return ParseResult(); + } + + ParseResult input = ctx.parse(arrayMember(value, 1), 1, {type::Number}); + if (!input) { + return input; + } + + std::map<double, std::unique_ptr<Expression>> stops; + optional<type::Type> outputType; + if (ctx.getExpected() && *ctx.getExpected() != type::Value) { + outputType = ctx.getExpected(); + } + + double previous = - std::numeric_limits<double>::infinity(); + + // consume the first output value, which doesn't have a corresponding input value, + // before proceeding into the "stops" loop below. + auto first_output = ctx.parse(arrayMember(value, 2), 2, outputType); + if (!first_output) { + return ParseResult(); + } + if (!outputType) { + outputType = (*first_output)->getType(); + } + stops.emplace(-std::numeric_limits<double>::infinity(), std::move(*first_output)); + + + for (std::size_t i = 3; i + 1 < length; i += 2) { + const optional<mbgl::Value> labelValue = toValue(arrayMember(value, i)); + optional<double> label; + optional<std::string> labelError; + if (labelValue) { + labelValue->match( + [&](uint64_t n) { + if (n > std::numeric_limits<double>::max()) { + label = {std::numeric_limits<double>::infinity()}; + } else { + label = {static_cast<double>(n)}; + } + }, + [&](int64_t n) { + if (n > std::numeric_limits<double>::max()) { + label = {std::numeric_limits<double>::infinity()}; + } else { + label = {static_cast<double>(n)}; + } + }, + [&](double n) { + if (n > std::numeric_limits<double>::max()) { + label = {std::numeric_limits<double>::infinity()}; + } else { + label = {static_cast<double>(n)}; + } + }, + [&](const auto&) {} + ); + } + if (!label) { + ctx.error(labelError ? *labelError : + R"(Input/output pairs for "step" expressions must be defined using literal numeric values (not computed expressions) for the input values.)", + i); + return ParseResult(); + } + + if (*label < previous) { + ctx.error( + R"(Input/output pairs for "step" expressions must be arranged with input values in strictly ascending order.)", + i + ); + return ParseResult(); + } + previous = *label; + + auto output = ctx.parse(arrayMember(value, i + 1), i + 1, outputType); + if (!output) { + return ParseResult(); + } + if (!outputType) { + outputType = (*output)->getType(); + } + + stops.emplace(*label, std::move(*output)); + } + + assert(outputType); + + return ParseResult(std::make_unique<Step>(*outputType, std::move(*input), std::move(stops))); +} + + +} // namespace expression +} // namespace style +} // namespace mbgl + diff --git a/test/fixtures/style_parser/expressions.info.json b/test/fixtures/style_parser/expressions.info.json index 49e0280d3f..9e1765ecd0 100644 --- a/test/fixtures/style_parser/expressions.info.json +++ b/test/fixtures/style_parser/expressions.info.json @@ -3,10 +3,10 @@ "log": [ [1, "WARNING", "ParseStyle", "Expected color but found number instead."], [1, "WARNING", "ParseStyle", "[2]: Expected number but found string instead."], - [1, "WARNING", "ParseStyle", "\"zoom\" expression may only be used as input to a top-level \"curve\" expression."], + [1, "WARNING", "ParseStyle", "\"zoom\" expression may only be used as input to a top-level \"step\" or \"interpolate\" expression."], [1, "WARNING", "ParseStyle", "value must be a string"], [1, "WARNING", "ParseStyle", "property expressions not supported"], - [1, "WARNING", "ParseStyle", "Type array<number> is not interpolatable, and thus cannot be used as a linear curve's output type."] + [1, "WARNING", "ParseStyle", "Type array<number> is not interpolatable."] ] } } diff --git a/test/fixtures/style_parser/expressions.style.json b/test/fixtures/style_parser/expressions.style.json index 389cd923ea..b9b4aeac7f 100644 --- a/test/fixtures/style_parser/expressions.style.json +++ b/test/fixtures/style_parser/expressions.style.json @@ -40,7 +40,7 @@ "source": "source", "source-layer": "layer", "paint": { - "fill-opacity": ["+", 0.5, ["curve", ["linear"], ["zoom"], 0, 0, 1, 1]] + "fill-opacity": ["+", 0.5, ["interpolate", ["linear"], ["zoom"], 0, 0, 1, 1]] } }, { @@ -67,7 +67,7 @@ "source": "source", "source-layer": "layer", "paint": { - "line-dasharray": ["curve", ["linear"], ["zoom"], 0, ["literal", [1, 2]], 1, ["literal", [3, 4]]] + "line-dasharray": ["interpolate", ["linear"], ["zoom"], 0, ["literal", [1, 2]], 1, ["literal", [3, 4]]] } } ] diff --git a/test/style/conversion/function.test.cpp b/test/style/conversion/function.test.cpp index 7a8b66f3e9..a48be2c075 100644 --- a/test/style/conversion/function.test.cpp +++ b/test/style/conversion/function.test.cpp @@ -62,19 +62,19 @@ TEST(StyleConversion, CompositeFunctionExpression) { return convert<DataDrivenPropertyValue<float>>(doc, error); }; - auto fn1 = parseFunction(R"(["curve", ["linear"], ["zoom"], 0, ["number", ["get", "x"]], 10, 10])"); + auto fn1 = parseFunction(R"(["interpolate", ["linear"], ["zoom"], 0, ["number", ["get", "x"]], 10, 10])"); ASSERT_TRUE(fn1); - auto fn2 = parseFunction(R"(["coalesce", ["curve", ["linear"], ["zoom"], 0, ["number", ["get", "x"]], 10, 10], 0])"); + auto fn2 = parseFunction(R"(["coalesce", ["interpolate", ["linear"], ["zoom"], 0, ["number", ["get", "x"]], 10, 10], 0])"); ASSERT_TRUE(fn2); - auto fn3 = parseFunction(R"(["let", "a", 0, ["curve", ["linear"], ["zoom"], 0, ["number", ["get", "x"]], 10, 10] ])"); + auto fn3 = parseFunction(R"(["let", "a", 0, ["interpolate", ["linear"], ["zoom"], 0, ["number", ["get", "x"]], 10, 10] ])"); ASSERT_TRUE(fn3); - auto fn4 = parseFunction(R"(["coalesce", ["let", "a", 0, ["curve", ["linear"], ["zoom"], 0, ["number", ["get", "x"]], 10, 10], 0 ])"); + auto fn4 = parseFunction(R"(["coalesce", ["let", "a", 0, ["interpolate", ["linear"], ["zoom"], 0, ["number", ["get", "x"]], 10, 10], 0 ])"); ASSERT_TRUE(fn4); - auto fn5 = parseFunction(R"(["coalesce", ["curve", ["linear"], ["number", ["get", "x"]], 0, ["zoom"], 10, 10], 0])"); + auto fn5 = parseFunction(R"(["coalesce", ["interpolate", ["linear"], ["number", ["get", "x"]], 0, ["zoom"], 10, 10], 0])"); ASSERT_FALSE(fn5); - ASSERT_EQ(R"("zoom" expression may only be used as input to a top-level "curve" expression.)", error.message); + ASSERT_EQ(R"("zoom" expression may only be used as input to a top-level "step" or "interpolate" expression.)", error.message); } |