[core] default value support in categorical function conversion

This commit is contained in:
Ivo van Dongen
2017-01-31 15:44:18 +02:00
committed by John Firebaugh
parent 141e995806
commit 8a5bff8ee6
19 changed files with 243 additions and 64 deletions

View File

@@ -50,6 +50,7 @@ set(MBGL_TEST_FILES
test/src/mbgl/test/fixture_log_observer.hpp
test/src/mbgl/test/stub_file_source.cpp
test/src/mbgl/test/stub_file_source.hpp
test/src/mbgl/test/stub_geometry_tile_feature.hpp
test/src/mbgl/test/stub_layer_observer.hpp
test/src/mbgl/test/stub_style_observer.hpp
test/src/mbgl/test/stub_tile_observer.hpp
@@ -80,6 +81,7 @@ set(MBGL_TEST_FILES
# style/function
test/style/function/camera_function.test.cpp
test/style/function/source_function.test.cpp
# style
test/style/group_by_layout.test.cpp

View File

@@ -198,6 +198,21 @@ struct Converter<CameraFunction<T>> {
}
};
template <class T, class V>
Result<optional<T>> convertDefaultValue(const V& value) {
auto defaultValueValue = objectMember(value, "defaultValue");
if (!defaultValueValue) {
return {};
}
auto defaultValue = convert<T>(*defaultValueValue);
if (!defaultValue) {
return Error { "wrong type for \"default\": " + defaultValue.error().message };
}
return *defaultValue;
}
template <class T>
struct Converter<SourceFunction<T>> {
template <class V>
@@ -221,7 +236,12 @@ struct Converter<SourceFunction<T>> {
return stops.error();
}
return SourceFunction<T>(*propertyString, *stops);
auto defaultValue = convertDefaultValue<T>(value);
if (!defaultValue) {
return defaultValue.error();
}
return SourceFunction<T>(*propertyString, *stops, *defaultValue);
}
};
@@ -280,6 +300,11 @@ struct Converter<CompositeFunction<T>> {
return Error { "function property must be a string" };
}
auto defaultValue = convertDefaultValue<T>(value);
if (!defaultValue) {
return defaultValue.error();
}
std::string type = "exponential";
auto typeValue = objectMember(value, "type");
if (typeValue && toString(*typeValue)) {
@@ -305,7 +330,7 @@ struct Converter<CompositeFunction<T>> {
inner.stops.emplace(stop.first.second, stop.second);
}
return CompositeFunction<T>(*propertyString, convertedStops);
return CompositeFunction<T>(*propertyString, convertedStops, *defaultValue);
} else if (type == "interval") {
auto stops = convertStops<CompositeValue<float>, T>(value);
if (!stops) {
@@ -318,7 +343,7 @@ struct Converter<CompositeFunction<T>> {
inner.stops.emplace(stop.first.second, stop.second);
}
return CompositeFunction<T>(*propertyString, convertedStops);
return CompositeFunction<T>(*propertyString, convertedStops, *defaultValue);
} else if (type == "categorical") {
auto stops = convertStops<CompositeValue<CategoricalValue>, T>(value);
if (!stops) {
@@ -331,7 +356,7 @@ struct Converter<CompositeFunction<T>> {
inner.stops.emplace(stop.first.second, stop.second);
}
return CompositeFunction<T>(*propertyString, convertedStops);
return CompositeFunction<T>(*propertyString, convertedStops, *defaultValue);
} else {
return Error { "unsupported function type" };
}

View File

@@ -25,7 +25,7 @@ public:
T evaluate(float zoom) const {
return stops.match([&] (const auto& s) {
return s.evaluate(Value(double(zoom)));
return s.evaluate(Value(double(zoom))).value_or(T());
});
}

View File

@@ -21,20 +21,18 @@ public:
using Stops = std::map<CategoricalValue, T>;
Stops stops;
T defaultValue;
CategoricalStops() = default;
CategoricalStops(Stops stops_, T defaultValue_ = T())
: stops(std::move(stops_)),
defaultValue(std::move(defaultValue_)) {
CategoricalStops(Stops stops_)
: stops(std::move(stops_)) {
assert(stops.size() > 0);
}
T evaluate(const Value&) const;
optional<T> evaluate(const Value&) const;
friend bool operator==(const CategoricalStops& lhs,
const CategoricalStops& rhs) {
return lhs.stops == rhs.stops && lhs.defaultValue == rhs.defaultValue;
return lhs.stops == rhs.stops;
}
};

View File

@@ -43,9 +43,10 @@ public:
std::map<float, IntervalStops<T>>,
std::map<float, CategoricalStops<T>>>>;
CompositeFunction(std::string property_, Stops stops_)
CompositeFunction(std::string property_, Stops stops_, optional<T> defaultValue_ = {})
: property(std::move(property_)),
stops(std::move(stops_)) {
stops(std::move(stops_)),
defaultValue(std::move(defaultValue_)) {
}
std::tuple<Range<float>, Range<InnerStops>>
@@ -73,13 +74,17 @@ public:
}
Range<T> evaluate(Range<InnerStops> coveringStops,
const GeometryTileFeature& feature) const {
const GeometryTileFeature& feature,
T finalDefaultValue) const {
optional<Value> v = feature.getValue(property);
if (!v) {
return { T(), T() };
return {
defaultValue.value_or(finalDefaultValue),
defaultValue.value_or(finalDefaultValue)
};
}
auto eval = [&] (const auto& s) {
return s.evaluate(*v);
return s.evaluate(*v).value_or(defaultValue.value_or(finalDefaultValue));
};
return Range<T> {
coveringStops.min.match(eval),
@@ -87,9 +92,9 @@ public:
};
}
T evaluate(float zoom, const GeometryTileFeature& feature) const {
T evaluate(float zoom, const GeometryTileFeature& feature, T finalDefaultValue) const {
std::tuple<Range<float>, Range<InnerStops>> ranges = coveringRanges(zoom);
Range<T> resultRange = evaluate(std::get<1>(ranges), feature);
Range<T> resultRange = evaluate(std::get<1>(ranges), feature, finalDefaultValue);
return util::interpolate(
resultRange.min,
resultRange.max,
@@ -98,11 +103,13 @@ public:
friend bool operator==(const CompositeFunction& lhs,
const CompositeFunction& rhs) {
return lhs.property == rhs.property && lhs.stops == rhs.stops;
return std::tie(lhs.property, lhs.stops, lhs.defaultValue)
== std::tie(rhs.property, rhs.stops, rhs.defaultValue);
}
std::string property;
Stops stops;
optional<T> defaultValue;
};
} // namespace style

View File

@@ -22,7 +22,7 @@ public:
base(base_) {
}
T evaluate(const Value& value) const {
optional<T> evaluate(const Value& value) const {
if (stops.empty()) {
assert(false);
return T();

View File

@@ -8,7 +8,7 @@ namespace style {
template <class T>
class IdentityStops {
public:
T evaluate(const Value&) const;
optional<T> evaluate(const Value&) const;
friend bool operator==(const IdentityStops&,
const IdentityStops&) {

View File

@@ -18,15 +18,15 @@ public:
: stops(std::move(stops_)) {
}
T evaluate(const Value& value) const {
optional<T> evaluate(const Value& value) const {
if (stops.empty()) {
assert(false);
return T();
return {};
}
optional<float> z = numericValue<float>(value);
if (!z) {
return T();
return {};
}
auto it = stops.upper_bound(*z);

View File

@@ -28,28 +28,31 @@ public:
CategoricalStops<T>,
IdentityStops<T>>>;
SourceFunction(std::string property_, Stops stops_)
SourceFunction(std::string property_, Stops stops_, optional<T> defaultValue_ = {})
: property(std::move(property_)),
stops(std::move(stops_)) {
stops(std::move(stops_)),
defaultValue(std::move(defaultValue_)) {
}
T evaluate(const GeometryTileFeature& feature) const {
T evaluate(const GeometryTileFeature& feature, T finalDefaultValue) const {
optional<Value> v = feature.getValue(property);
if (!v) {
return T();
return defaultValue.value_or(finalDefaultValue);
}
return stops.match([&] (const auto& s) {
return s.evaluate(*v);
return stops.match([&] (const auto& s) -> T {
return s.evaluate(*v).value_or(defaultValue.value_or(finalDefaultValue));
});
}
friend bool operator==(const SourceFunction& lhs,
const SourceFunction& rhs) {
return lhs.property == rhs.property && lhs.stops == rhs.stops;
return std::tie(lhs.property, lhs.stops, lhs.defaultValue)
== std::tie(rhs.property, rhs.stops, rhs.defaultValue);
}
std::string property;
Stops stops;
optional<T> defaultValue;
};
} // namespace style

View File

@@ -129,8 +129,8 @@ SymbolLayout::SymbolLayout(const BucketParameters& parameters,
if (hasIcon) {
ft.icon = util::replaceTokens(layout.get<IconImage>(), getValue);
ft.iconOffset = layout.get<IconOffset>().evaluate(zoom, *feature);
ft.iconRotation = layout.get<IconRotate>().evaluate(zoom, *feature) * util::DEG2RAD;
ft.iconOffset = layout.evaluate<IconOffset>(zoom, *feature);
ft.iconRotation = layout.evaluate<IconRotate>(zoom, *feature) * util::DEG2RAD;
}
if (ft.text || ft.icon) {

View File

@@ -330,6 +330,10 @@ void stringify(Writer& writer, const SourceFunction<T>& f) {
writer.Key("property");
writer.String(f.property);
SourceFunction<T>::Stops::visit(f.stops, StringifyStops<Writer> { writer });
if (f.defaultValue) {
writer.Key("default");
stringify(writer, *f.defaultValue);
}
writer.EndObject();
}
@@ -339,6 +343,10 @@ void stringify(Writer& writer, const CompositeFunction<T>& f) {
writer.Key("property");
writer.String(f.property);
CompositeFunction<T>::Stops::visit(f.stops, StringifyStops<Writer> { writer });
if (f.defaultValue) {
writer.Key("default");
stringify(writer, *f.defaultValue);
}
writer.EndObject();
}

View File

@@ -18,13 +18,13 @@ optional<CategoricalValue> categoricalValue(const Value& value) {
}
template <class T>
T CategoricalStops<T>::evaluate(const Value& value) const {
optional<T> CategoricalStops<T>::evaluate(const Value& value) const {
auto v = categoricalValue(value);
if (!v) {
return defaultValue;
return {};
}
auto it = stops.find(*v);
return it == stops.end() ? defaultValue : it->second;
return it == stops.end() ? optional<T>() : it->second;
}
template class CategoricalStops<float>;

View File

@@ -7,36 +7,34 @@ namespace mbgl {
namespace style {
template <>
float IdentityStops<float>::evaluate(const Value& value) const {
return numericValue<float>(value)
.value_or(0.0f);
optional<float> IdentityStops<float>::evaluate(const Value& value) const {
return numericValue<float>(value);
}
template <>
Color IdentityStops<Color>::evaluate(const Value& value) const {
optional<Color> IdentityStops<Color>::evaluate(const Value& value) const {
if (!value.is<std::string>()) {
return Color::black();
return {};
}
return Color::parse(value.get<std::string>())
.value_or(Color::black());
return Color::parse(value.get<std::string>());
}
template <>
std::array<float, 2> IdentityStops<std::array<float, 2>>::evaluate(const Value& value) const {
optional<std::array<float, 2>> IdentityStops<std::array<float, 2>>::evaluate(const Value& value) const {
if (!value.is<std::vector<Value>>()) {
return {{ 0, 0 }};
return {};
}
const std::vector<Value>& vector = value.get<std::vector<Value>>();
if (vector.size() != 2 || !numericValue<float>(vector[0]) || !numericValue<float>(vector[1])) {
return {{ 0, 0 }};
return {};
}
return {{
return {{{
*numericValue<float>(vector[0]),
*numericValue<float>(vector[1])
}};
}}};
}
} // namespace style

View File

@@ -17,6 +17,7 @@ public:
using UnevaluatedType = PropertyValue<T>;
using EvaluatorType = PropertyEvaluator<T>;
using EvaluatedType = T;
using Type = T;
};
template <class T>
@@ -25,6 +26,7 @@ public:
using UnevaluatedType = DataDrivenPropertyValue<T>;
using EvaluatorType = DataDrivenPropertyEvaluator<T>;
using EvaluatedType = PossiblyEvaluatedPropertyValue<T>;
using Type = T;
};
template <class... Ps>
@@ -40,6 +42,15 @@ public:
class Evaluated : public Tuple<EvaluatedTypes> {
public:
using Tuple<EvaluatedTypes>::Tuple;
template <class P>
typename P::Type evaluate(float z, const GeometryTileFeature& feature) const {
using T = typename P::Type;
return this->template get<P>().match(
[&] (const T& t) { return t; },
[&] (const SourceFunction<T>& t) { return t.evaluate(feature, P::defaultValue()); },
[&] (const CompositeFunction<T>& t) { return t.evaluate(z, feature, P::defaultValue()); });
}
};
class Unevaluated : public Tuple<UnevaluatedTypes> {

View File

@@ -50,12 +50,13 @@ public:
using Attributes = gl::Attributes<Attribute>;
using Vertex = typename Attributes::Vertex;
SourceFunctionPaintPropertyBinder(SourceFunction<T> function_)
: function(std::move(function_)) {
SourceFunctionPaintPropertyBinder(SourceFunction<T> function_, T defaultValue_)
: function(std::move(function_)),
defaultValue(std::move(defaultValue_)) {
}
void populateVertexVector(const GeometryTileFeature& feature, std::size_t length) {
AttributeValue value = Attribute::value(function.evaluate(feature));
AttributeValue value = Attribute::value(function.evaluate(feature, defaultValue));
for (std::size_t i = vertexVector.vertexSize(); i < length; ++i) {
vertexVector.emplace_back(Vertex { value });
}
@@ -86,6 +87,7 @@ public:
private:
SourceFunction<T> function;
T defaultValue;
gl::VertexVector<Vertex> vertexVector;
optional<gl::VertexBuffer<Vertex>> vertexBuffer;
};
@@ -103,13 +105,14 @@ public:
using Attributes = gl::Attributes<MinAttribute, MaxAttribute>;
using Vertex = typename Attributes::Vertex;
CompositeFunctionPaintPropertyBinder(CompositeFunction<T> function_, float zoom)
CompositeFunctionPaintPropertyBinder(CompositeFunction<T> function_, float zoom, T defaultValue_)
: function(std::move(function_)),
defaultValue(std::move(defaultValue_)),
coveringRanges(function.coveringRanges(zoom)) {
}
void populateVertexVector(const GeometryTileFeature& feature, std::size_t length) {
Range<T> range = function.evaluate(std::get<1>(coveringRanges), feature);
Range<T> range = function.evaluate(std::get<1>(coveringRanges), feature, defaultValue);
AttributeValue min = Attribute::value(range.min);
AttributeValue max = Attribute::value(range.max);
for (std::size_t i = vertexVector.vertexSize(); i < length; ++i) {
@@ -148,6 +151,7 @@ public:
private:
using InnerStops = typename CompositeFunction<T>::InnerStops;
CompositeFunction<T> function;
T defaultValue;
std::tuple<Range<float>, Range<InnerStops>> coveringRanges;
gl::VertexVector<Vertex> vertexVector;
optional<gl::VertexBuffer<Vertex>> vertexBuffer;
@@ -171,10 +175,10 @@ public:
return ConstantPaintPropertyBinder<Type, Attribute>(constant);
},
[&] (const SourceFunction<Type>& function) {
return SourceFunctionPaintPropertyBinder<Type, Attribute>(function);
return SourceFunctionPaintPropertyBinder<Type, Attribute>(function, PaintProperty::defaultValue());
},
[&] (const CompositeFunction<Type>& function) {
return CompositeFunctionPaintPropertyBinder<Type, Attribute>(function, zoom);
return CompositeFunctionPaintPropertyBinder<Type, Attribute>(function, zoom, PaintProperty::defaultValue());
}
)) {
}

View File

@@ -43,13 +43,6 @@ public:
auto match(Ts&&... ts) const {
return value.match(std::forward<Ts>(ts)...);
}
T evaluate(float z, const GeometryTileFeature& feature) const {
return value.match(
[&] (const T& t) { return t; },
[&] (const SourceFunction<T>& t) { return t.evaluate(feature); },
[&] (const CompositeFunction<T>& t) { return t.evaluate(z, feature); });
}
};
} // namespace style

View File

@@ -0,0 +1,34 @@
#include <mbgl/tile/geometry_tile_data.hpp>
#include <mbgl/util/feature.hpp>
namespace mbgl {
class StubGeometryTileFeature : public GeometryTileFeature {
public:
StubGeometryTileFeature(PropertyMap properties_)
: properties(std::move(properties_)) {
}
PropertyMap properties;
optional<FeatureIdentifier> id = {};
FeatureType type = FeatureType::Point;
GeometryCollection geometry = {};
FeatureType getType() const override {
return type;
}
optional<FeatureIdentifier> getID() const override {
return id;
}
optional<Value> getValue(const std::string& key) const override {
return properties.count(key) ? properties.at(key) : optional<Value>();
}
GeometryCollection getGeometries() const override {
return geometry;
}
};
} // namespace mbgl

View File

@@ -95,6 +95,8 @@ TEST(Stringify, SourceFunction) {
"{\"property\":\"property\",\"type\":\"categorical\",\"stops\":[[true,1.0]]}");
ASSERT_EQ(stringify(SourceFunction<float>("property", IdentityStops<float> {})),
"{\"property\":\"property\",\"type\":\"identity\"}");
ASSERT_EQ(stringify(SourceFunction<float>("property", IdentityStops<float> {}, 0.0f)),
"{\"property\":\"property\",\"type\":\"identity\",\"default\":0.0}");
}
TEST(Stringify, CompositeFunction) {
@@ -102,11 +104,11 @@ TEST(Stringify, CompositeFunction) {
std::map<float, ExponentialStops<float>> {
{ 0, ExponentialStops<float> { {{0, 1}}, 2 } },
{ 1, ExponentialStops<float> { {{0, 1}}, 2 } }
})),
}, 0.0f)),
"{\"property\":\"property\",\"type\":\"exponential\",\"base\":2.0,"
"\"stops\":["
"[{\"zoom\":0.0,\"value\":0.0},1.0],"
"[{\"zoom\":1.0,\"value\":0.0},1.0]]}");
"[{\"zoom\":1.0,\"value\":0.0},1.0]],\"default\":0.0}");
}
TEST(Stringify, PropertyValue) {

View File

@@ -0,0 +1,94 @@
#include <mbgl/test/util.hpp>
#include <mbgl/test/stub_geometry_tile_feature.hpp>
#include <mbgl/style/function/source_function.hpp>
using namespace mbgl;
using namespace mbgl::style;
using namespace std::string_literals;
static StubGeometryTileFeature oneInteger {
PropertyMap {{ "property", uint64_t(1) }}
};
static StubGeometryTileFeature oneDouble {
PropertyMap {{ "property", 1.0 }}
};
static StubGeometryTileFeature oneString {
PropertyMap {{ "property", "1"s }}
};
static StubGeometryTileFeature red {
PropertyMap {{ "property", "red"s }}
};
static StubGeometryTileFeature oneTwoInteger {
PropertyMap {{ "property", std::vector<Value>({uint64_t(1), uint64_t(2)}) }}
};
static StubGeometryTileFeature oneTwoDouble {
PropertyMap {{ "property", std::vector<Value>({1.0, 2.0}) }}
};
static StubGeometryTileFeature oneTwoString {
PropertyMap {{ "property", std::vector<Value>({"1"s, "2"s}) }}
};
static StubGeometryTileFeature trueFeature {
PropertyMap {{ "property", true }}
};
static StubGeometryTileFeature falseFeature {
PropertyMap {{ "property", false }}
};
TEST(SourceFunction, Identity) {
EXPECT_EQ(1.0f, SourceFunction<float>("property", IdentityStops<float>(), 0.0f)
.evaluate(oneInteger, 2.0f));
EXPECT_EQ(1.0f, SourceFunction<float>("property", IdentityStops<float>(), 0.0f)
.evaluate(oneDouble, 2.0f));
EXPECT_EQ(0.0f, SourceFunction<float>("property", IdentityStops<float>(), 0.0f)
.evaluate(oneString, 2.0f));
EXPECT_EQ(2.0f, SourceFunction<float>("property", IdentityStops<float>())
.evaluate(oneString, 2.0f));
EXPECT_EQ(Color::red(), SourceFunction<Color>("property", IdentityStops<Color>(), Color::black())
.evaluate(red, Color::black()));
EXPECT_EQ(Color::black(), SourceFunction<Color>("property", IdentityStops<Color>(), Color::black())
.evaluate(oneInteger, Color::black()));
std::array<float, 2> zeroArray {{ 0, 0 }};
EXPECT_EQ((std::array<float, 2> {{ 1, 2 }}), (SourceFunction<std::array<float, 2>>("property", IdentityStops<std::array<float, 2>>(), zeroArray)
.evaluate(oneTwoInteger, zeroArray)));
EXPECT_EQ((std::array<float, 2> {{ 1, 2 }}), (SourceFunction<std::array<float, 2>>("property", IdentityStops<std::array<float, 2>>(), zeroArray)
.evaluate(oneTwoDouble, zeroArray)));
EXPECT_EQ((std::array<float, 2> {{ 0, 0 }}), (SourceFunction<std::array<float, 2>>("property", IdentityStops<std::array<float, 2>>(), zeroArray)
.evaluate(oneTwoString, zeroArray)));
}
TEST(SourceFunction, Categorical) {
EXPECT_EQ(1.0f, SourceFunction<float>("property", CategoricalStops<float>({{ int64_t(1), 1.0f }}))
.evaluate(oneInteger, 0.0f));
EXPECT_EQ(1.0f, SourceFunction<float>("property", CategoricalStops<float>({{ int64_t(1), 1.0f }}))
.evaluate(oneDouble, 0.0f));
EXPECT_EQ(0.0f, SourceFunction<float>("property", CategoricalStops<float>({{ int64_t(1), 1.0f }}))
.evaluate(oneString, 0.0f));
EXPECT_EQ(0.0f, SourceFunction<float>("property", CategoricalStops<float>({{ "1"s, 1.0f }}))
.evaluate(oneInteger, 0.0f));
EXPECT_EQ(0.0f, SourceFunction<float>("property", CategoricalStops<float>({{ "1"s, 1.0f }}))
.evaluate(oneDouble, 0.0f));
EXPECT_EQ(1.0f, SourceFunction<float>("property", CategoricalStops<float>({{ "1"s, 1.0f }}))
.evaluate(oneString, 0.0f));
EXPECT_EQ(1.0f, SourceFunction<float>("property", CategoricalStops<float>({{ true, 1.0f }}))
.evaluate(trueFeature, 0.0f));
EXPECT_EQ(0.0f, SourceFunction<float>("property", CategoricalStops<float>({{ true, 1.0f }}))
.evaluate(falseFeature, 0.0f));
EXPECT_EQ(0.0f, SourceFunction<float>("property", CategoricalStops<float>({{ false, 1.0f }}))
.evaluate(trueFeature, 0.0f));
EXPECT_EQ(1.0f, SourceFunction<float>("property", CategoricalStops<float>({{ false, 1.0f }}))
.evaluate(falseFeature, 0.0f));
}