[core] Convert "legacy" filters directly into expressions (#11610)

Ports the specialized filter-* expressions from GL JS, adding them to src/mbgl/style/expression/compound_expression.cpp
This commit is contained in:
Lucas Wojciechowski
2018-05-10 12:37:14 -07:00
committed by Asheem Mamoowala
parent ee10088702
commit a4e2c1af1f
34 changed files with 586 additions and 1415 deletions

View File

@@ -1,7 +1,6 @@
#include <benchmark/benchmark.h>
#include <mbgl/style/filter.hpp>
#include <mbgl/style/filter_evaluator.hpp>
#include <mbgl/style/conversion.hpp>
#include <mbgl/style/conversion/json.hpp>
#include <mbgl/style/conversion/filter.hpp>

View File

@@ -360,7 +360,6 @@ set(MBGL_CORE_FILES
include/mbgl/style/conversion.hpp
include/mbgl/style/data_driven_property_value.hpp
include/mbgl/style/filter.hpp
include/mbgl/style/filter_evaluator.hpp
include/mbgl/style/heatmap_color_property_value.hpp
include/mbgl/style/image.hpp
include/mbgl/style/layer.hpp
@@ -377,7 +376,6 @@ set(MBGL_CORE_FILES
src/mbgl/style/custom_tile_loader.cpp
src/mbgl/style/custom_tile_loader.hpp
src/mbgl/style/filter.cpp
src/mbgl/style/filter_evaluator.cpp
src/mbgl/style/image.cpp
src/mbgl/style/image_impl.cpp
src/mbgl/style/image_impl.hpp

View File

@@ -36,7 +36,7 @@ template <typename T>
struct Varargs : std::vector<T> { using std::vector<T>::vector; };
namespace detail {
// Base class for the Signature<Fn> structs that are used to determine the
// Base class for the Signature<Fn> structs that are used to determine
// each CompoundExpression definition's type::Type data from the type of its
// "evaluate" function.
struct SignatureBase {
@@ -137,10 +137,21 @@ ParseResult parseCompoundExpression(const std::string name, const mbgl::style::c
ParseResult createCompoundExpression(const CompoundExpressionRegistry::Definition& definition,
std::vector<std::unique_ptr<Expression>> args,
ParsingContext& ctx);
ParseResult createCompoundExpression(const std::string& name,
std::vector<std::unique_ptr<Expression>> args,
ParsingContext& ctx);
// Convenience method for use expressions that have 0, 1, or 2 args.
ParseResult createCompoundExpression(const std::string& name, ParsingContext& ctx);
ParseResult createCompoundExpression(const std::string& name,
std::unique_ptr<Expression> arg1,
ParsingContext& ctx);
ParseResult createCompoundExpression(const std::string& name,
std::unique_ptr<Expression> arg1,
std::unique_ptr<Expression> arg2,
ParsingContext& ctx);
} // namespace expression
} // namespace style

View File

@@ -51,6 +51,9 @@ private:
Value value;
};
std::unique_ptr<Literal> createLiteral(const char* value);
std::unique_ptr<Literal> createLiteral(Value value);
} // namespace expression
} // namespace style
} // namespace mbgl

View File

@@ -11,270 +11,30 @@
namespace mbgl {
namespace style {
class Filter;
class NullFilter {
public:
friend bool operator==(const NullFilter&, const NullFilter&) {
return true;
}
};
class EqualsFilter {
public:
std::string key;
Value value;
friend bool operator==(const EqualsFilter& lhs, const EqualsFilter& rhs) {
return std::tie(lhs.key, lhs.value) == std::tie(rhs.key, rhs.value);
}
};
class NotEqualsFilter {
public:
std::string key;
Value value;
friend bool operator==(const NotEqualsFilter& lhs, const NotEqualsFilter& rhs) {
return std::tie(lhs.key, lhs.value) == std::tie(rhs.key, rhs.value);
}
};
class LessThanFilter {
public:
std::string key;
Value value;
friend bool operator==(const LessThanFilter& lhs, const LessThanFilter& rhs) {
return std::tie(lhs.key, lhs.value) == std::tie(rhs.key, rhs.value);
}
};
class LessThanEqualsFilter {
public:
std::string key;
Value value;
friend bool operator==(const LessThanEqualsFilter& lhs, const LessThanEqualsFilter& rhs) {
return std::tie(lhs.key, lhs.value) == std::tie(rhs.key, rhs.value);
}
};
class GreaterThanFilter {
public:
std::string key;
Value value;
friend bool operator==(const GreaterThanFilter& lhs, const GreaterThanFilter& rhs) {
return std::tie(lhs.key, lhs.value) == std::tie(rhs.key, rhs.value);
}
};
class GreaterThanEqualsFilter {
public:
std::string key;
Value value;
friend bool operator==(const GreaterThanEqualsFilter& lhs, const GreaterThanEqualsFilter& rhs) {
return std::tie(lhs.key, lhs.value) == std::tie(rhs.key, rhs.value);
}
};
class InFilter {
public:
std::string key;
std::vector<Value> values;
friend bool operator==(const InFilter& lhs, const InFilter& rhs) {
return std::tie(lhs.key, lhs.values) == std::tie(rhs.key, rhs.values);
}
};
class NotInFilter {
public:
std::string key;
std::vector<Value> values;
friend bool operator==(const NotInFilter& lhs, const NotInFilter& rhs) {
return std::tie(lhs.key, lhs.values) == std::tie(rhs.key, rhs.values);
}
};
class AnyFilter {
public:
std::vector<Filter> filters;
friend bool operator==(const AnyFilter& lhs, const AnyFilter& rhs) {
return lhs.filters == rhs.filters;
}
};
class AllFilter {
public:
std::vector<Filter> filters;
friend bool operator==(const AllFilter& lhs, const AllFilter& rhs) {
return lhs.filters == rhs.filters;
}
};
class NoneFilter {
public:
std::vector<Filter> filters;
friend bool operator==(const NoneFilter& lhs, const NoneFilter& rhs) {
return lhs.filters == rhs.filters;
}
};
class HasFilter {
public:
std::string key;
friend bool operator==(const HasFilter& lhs, const HasFilter& rhs) {
return lhs.key == rhs.key;
}
};
class NotHasFilter {
public:
std::string key;
friend bool operator==(const NotHasFilter& lhs, const NotHasFilter& rhs) {
return lhs.key == rhs.key;
}
};
class TypeEqualsFilter {
public:
FeatureType value;
friend bool operator==(const TypeEqualsFilter& lhs, const TypeEqualsFilter& rhs) {
return lhs.value == rhs.value;
}
};
class TypeNotEqualsFilter {
public:
FeatureType value;
friend bool operator==(const TypeNotEqualsFilter& lhs, const TypeNotEqualsFilter& rhs) {
return lhs.value == rhs.value;
}
};
class TypeInFilter {
public:
std::vector<FeatureType> values;
friend bool operator==(const TypeInFilter& lhs, const TypeInFilter& rhs) {
return lhs.values == rhs.values;
}
};
class TypeNotInFilter {
public:
std::vector<FeatureType> values;
friend bool operator==(const TypeNotInFilter& lhs, const TypeNotInFilter& rhs) {
return lhs.values == rhs.values;
}
};
class IdentifierEqualsFilter {
public:
FeatureIdentifier value;
friend bool operator==(const IdentifierEqualsFilter& lhs, const IdentifierEqualsFilter& rhs) {
return lhs.value == rhs.value;
}
};
class IdentifierNotEqualsFilter {
public:
FeatureIdentifier value;
friend bool operator==(const IdentifierNotEqualsFilter& lhs, const IdentifierNotEqualsFilter& rhs) {
return lhs.value == rhs.value;
}
};
class IdentifierInFilter {
public:
std::vector<FeatureIdentifier> values;
friend bool operator==(const IdentifierInFilter& lhs, const IdentifierInFilter& rhs) {
return lhs.values == rhs.values;
}
};
class IdentifierNotInFilter {
public:
std::vector<FeatureIdentifier> values;
friend bool operator==(const IdentifierNotInFilter& lhs, const IdentifierNotInFilter& rhs) {
return lhs.values == rhs.values;
}
};
class HasIdentifierFilter {
public:
friend bool operator==(const HasIdentifierFilter&, const HasIdentifierFilter&) {
return true;
}
};
class NotHasIdentifierFilter {
public:
friend bool operator==(const NotHasIdentifierFilter&, const NotHasIdentifierFilter&) {
return true;
}
};
class ExpressionFilter {
class Filter {
public:
std::shared_ptr<const expression::Expression> expression;
optional<std::shared_ptr<const expression::Expression>> expression;
friend bool operator==(const ExpressionFilter& lhs, const ExpressionFilter& rhs) {
return *(lhs.expression) == *(rhs.expression);
Filter() : expression() {}
Filter(expression::ParseResult _expression) : expression(std::move(*_expression)) {
assert(!expression || *expression != nullptr);
}
};
using FilterBase = variant<
class NullFilter,
class EqualsFilter,
class NotEqualsFilter,
class LessThanFilter,
class LessThanEqualsFilter,
class GreaterThanFilter,
class GreaterThanEqualsFilter,
class InFilter,
class NotInFilter,
class AnyFilter,
class AllFilter,
class NoneFilter,
class HasFilter,
class NotHasFilter,
class TypeEqualsFilter,
class TypeNotEqualsFilter,
class TypeInFilter,
class TypeNotInFilter,
class IdentifierEqualsFilter,
class IdentifierNotEqualsFilter,
class IdentifierInFilter,
class IdentifierNotInFilter,
class HasIdentifierFilter,
class NotHasIdentifierFilter,
class ExpressionFilter>;
class Filter : public FilterBase {
public:
using FilterBase::FilterBase;
bool operator()(const expression::EvaluationContext& context) const;
friend bool operator==(const Filter& lhs, const Filter& rhs) {
if (!lhs.expression || !rhs.expression) {
return lhs.expression == rhs.expression;
} else {
return *(lhs.expression) == *(rhs.expression);
}
}
friend bool operator!=(const Filter& lhs, const Filter& rhs) {
return !(lhs == rhs);
}
};
} // namespace style

View File

@@ -1,55 +0,0 @@
#pragma once
#include <mbgl/style/filter.hpp>
#include <mbgl/util/geometry.hpp>
#include <type_traits>
namespace mbgl {
namespace style {
/*
A visitor that evaluates a `Filter` for a given feature.
Use via `Filter::operator()`. For example:
if (filter(feature)) {
// matches the filter
} else {
// does not match
}
*/
class FilterEvaluator {
public:
const expression::EvaluationContext context;
bool operator()(const NullFilter&) const;
bool operator()(const EqualsFilter& filter) const;
bool operator()(const NotEqualsFilter& filter) const;
bool operator()(const LessThanFilter& filter) const;
bool operator()(const LessThanEqualsFilter& filter) const;
bool operator()(const GreaterThanFilter& filter) const;
bool operator()(const GreaterThanEqualsFilter& filter) const;
bool operator()(const InFilter& filter) const;
bool operator()(const NotInFilter& filter) const;
bool operator()(const AnyFilter& filter) const;
bool operator()(const AllFilter& filter) const;
bool operator()(const NoneFilter& filter) const;
bool operator()(const HasFilter& filter) const;
bool operator()(const NotHasFilter& filter) const;
bool operator()(const TypeEqualsFilter& filter) const;
bool operator()(const TypeNotEqualsFilter& filter) const;
bool operator()(const TypeInFilter& filter) const;
bool operator()(const TypeNotInFilter& filter) const;
bool operator()(const IdentifierEqualsFilter& filter) const;
bool operator()(const IdentifierNotEqualsFilter& filter) const;
bool operator()(const IdentifierInFilter& filter) const;
bool operator()(const IdentifierNotInFilter& filter) const;
bool operator()(const HasIdentifierFilter&) const;
bool operator()(const NotHasIdentifierFilter&) const;
bool operator()(const ExpressionFilter&) const;
};
} // namespace style
} // namespace mbgl

View File

@@ -157,14 +157,12 @@ namespace android {
using namespace mbgl::style::conversion;
Filter filter = layer.accept(GetFilterEvaluator());
jni::Object<gson::JsonElement> converted;
if (filter.is<ExpressionFilter>()) {
ExpressionFilter filterExpression = filter.get<ExpressionFilter>();
mbgl::Value expressionValue = filterExpression.expression.get()->serialize();
converted = gson::JsonElement::New(env, expressionValue);
if (filter.expression) {
mbgl::Value expressionValue = (*filter.expression)->serialize();
return gson::JsonElement::New(env, expressionValue);
} else {
return jni::Object<gson::JsonElement>();
}
return converted;
}
struct SetSourceLayerEvaluator {

View File

@@ -75,7 +75,7 @@ namespace mbgl {
{
MGLAssertStyleLayerIsValid();
self.rawLayer->setFilter(predicate ? predicate.mgl_filter : mbgl::style::NullFilter());
self.rawLayer->setFilter(predicate ? predicate.mgl_filter : mbgl::style::Filter());
}
- (NSPredicate *)predicate

View File

@@ -65,7 +65,7 @@ namespace mbgl {
{
MGLAssertStyleLayerIsValid();
self.rawLayer->setFilter(predicate ? predicate.mgl_filter : mbgl::style::NullFilter());
self.rawLayer->setFilter(predicate ? predicate.mgl_filter : mbgl::style::Filter());
}
- (NSPredicate *)predicate

View File

@@ -65,7 +65,7 @@ namespace mbgl {
{
MGLAssertStyleLayerIsValid();
self.rawLayer->setFilter(predicate ? predicate.mgl_filter : mbgl::style::NullFilter());
self.rawLayer->setFilter(predicate ? predicate.mgl_filter : mbgl::style::Filter());
}
- (NSPredicate *)predicate

View File

@@ -56,7 +56,7 @@
{
MGLAssertStyleLayerIsValid();
self.rawLayer->setFilter(predicate ? predicate.mgl_filter : mbgl::style::NullFilter());
self.rawLayer->setFilter(predicate ? predicate.mgl_filter : mbgl::style::Filter());
}
- (NSPredicate *)predicate

View File

@@ -77,7 +77,7 @@ namespace mbgl {
{
MGLAssertStyleLayerIsValid();
self.rawLayer->setFilter(predicate ? predicate.mgl_filter : mbgl::style::NullFilter());
self.rawLayer->setFilter(predicate ? predicate.mgl_filter : mbgl::style::Filter());
}
- (NSPredicate *)predicate

View File

@@ -103,7 +103,7 @@ namespace mbgl {
{
MGLAssertStyleLayerIsValid();
self.rawLayer->setFilter(predicate ? predicate.mgl_filter : mbgl::style::NullFilter());
self.rawLayer->setFilter(predicate ? predicate.mgl_filter : mbgl::style::Filter());
}
- (NSPredicate *)predicate

View File

@@ -142,7 +142,7 @@ namespace mbgl {
{
MGLAssertStyleLayerIsValid();
self.rawLayer->setFilter(predicate ? predicate.mgl_filter : mbgl::style::NullFilter());
self.rawLayer->setFilter(predicate ? predicate.mgl_filter : mbgl::style::Filter());
}
- (NSPredicate *)predicate

View File

@@ -5,205 +5,6 @@
#include <mbgl/style/conversion/filter.hpp>
class FilterEvaluator {
public:
NSArray *getPredicates(std::vector<mbgl::style::Filter> filters) {
NSMutableArray *predicates = [NSMutableArray arrayWithCapacity:filters.size()];
for (auto filter : filters) {
[predicates addObject:mbgl::style::Filter::visit(filter, FilterEvaluator())];
}
return predicates;
}
template <typename MBGLType>
NSExpression *getValues(std::vector<MBGLType> values) {
NSMutableArray *array = [NSMutableArray arrayWithCapacity:values.size()];
for (auto value : values) {
id constantValue = MBGLType::visit(value, ValueEvaluator());
[array addObject:[NSExpression expressionForConstantValue:constantValue]];
}
return [NSExpression expressionForAggregate:array];
}
NSString *getFeatureTypeString(mbgl::FeatureType type) {
switch (type) {
case mbgl::FeatureType::Point:
return @"Point";
case mbgl::FeatureType::LineString:
return @"LineString";
case mbgl::FeatureType::Polygon:
return @"Polygon";
default:
NSCAssert(NO, @"Unrecognized feature type %hhu", type);
return nil;
}
}
NSExpression *getFeatureTypeStrings(std::vector<mbgl::FeatureType> values) {
NSMutableArray *array = [NSMutableArray arrayWithCapacity:values.size()];
for (auto value : values) {
id typeString = getFeatureTypeString(value);
[array addObject:[NSExpression expressionForConstantValue:typeString]];
}
return [NSExpression expressionForAggregate:array];
}
NSPredicate *operator()(mbgl::style::NullFilter filter) {
return nil;
}
NSPredicate *operator()(mbgl::style::EqualsFilter filter) {
return [NSPredicate predicateWithFormat:@"%K == %@", @(filter.key.c_str()), mbgl::Value::visit(filter.value, ValueEvaluator())];
}
NSPredicate *operator()(mbgl::style::NotEqualsFilter filter) {
return [NSPredicate predicateWithFormat:@"%K != %@", @(filter.key.c_str()), mbgl::Value::visit(filter.value, ValueEvaluator())];
}
NSPredicate *operator()(mbgl::style::GreaterThanFilter filter) {
return [NSPredicate predicateWithFormat:@"%K > %@", @(filter.key.c_str()), mbgl::Value::visit(filter.value, ValueEvaluator())];
}
NSPredicate *operator()(mbgl::style::GreaterThanEqualsFilter filter) {
return [NSPredicate predicateWithFormat:@"%K >= %@", @(filter.key.c_str()), mbgl::Value::visit(filter.value, ValueEvaluator())];
}
NSPredicate *operator()(mbgl::style::LessThanFilter filter) {
return [NSPredicate predicateWithFormat:@"%K < %@", @(filter.key.c_str()), mbgl::Value::visit(filter.value, ValueEvaluator())];
}
NSPredicate *operator()(mbgl::style::LessThanEqualsFilter filter) {
return [NSPredicate predicateWithFormat:@"%K <= %@", @(filter.key.c_str()), mbgl::Value::visit(filter.value, ValueEvaluator())];
}
NSPredicate *operator()(mbgl::style::InFilter filter) {
return [NSPredicate predicateWithFormat:@"%K IN %@", @(filter.key.c_str()), getValues(filter.values)];
}
NSPredicate *operator()(mbgl::style::NotInFilter filter) {
return [NSPredicate predicateWithFormat:@"NOT %K IN %@", @(filter.key.c_str()), getValues(filter.values)];
}
NSPredicate *operator()(mbgl::style::TypeEqualsFilter filter) {
return [NSPredicate predicateWithFormat:@"%K == %@", @"$type", getFeatureTypeString(filter.value)];
}
NSPredicate *operator()(mbgl::style::TypeNotEqualsFilter filter) {
return [NSPredicate predicateWithFormat:@"%K != %@", @"$type", getFeatureTypeString(filter.value)];
}
NSPredicate *operator()(mbgl::style::TypeInFilter filter) {
return [NSPredicate predicateWithFormat:@"%K IN %@", @"$type", getFeatureTypeStrings(filter.values)];
}
NSPredicate *operator()(mbgl::style::TypeNotInFilter filter) {
return [NSPredicate predicateWithFormat:@"NOT %K IN %@", @"$type", getFeatureTypeStrings(filter.values)];
}
NSPredicate *operator()(mbgl::style::IdentifierEqualsFilter filter) {
return [NSPredicate predicateWithFormat:@"%K == %@", @"$id", mbgl::FeatureIdentifier::visit(filter.value, ValueEvaluator())];
}
NSPredicate *operator()(mbgl::style::IdentifierNotEqualsFilter filter) {
return [NSPredicate predicateWithFormat:@"%K != %@", @"$id", mbgl::FeatureIdentifier::visit(filter.value, ValueEvaluator())];
}
NSPredicate *operator()(mbgl::style::IdentifierInFilter filter) {
return [NSPredicate predicateWithFormat:@"%K IN %@", @"$id", getValues(filter.values)];
}
NSPredicate *operator()(mbgl::style::IdentifierNotInFilter filter) {
return [NSPredicate predicateWithFormat:@"NOT %K IN %@", @"$id", getValues(filter.values)];
}
NSPredicate *operator()(mbgl::style::AnyFilter filter) {
NSArray *subpredicates = getPredicates(filter.filters);
if (subpredicates.count) {
return [NSCompoundPredicate orPredicateWithSubpredicates:subpredicates];
}
return [NSPredicate predicateWithValue:NO];
}
NSPredicate *operator()(mbgl::style::AllFilter filter) {
// Convert [all, [>=, key, lower], [<=, key, upper]] to key BETWEEN {lower, upper}
if (filter.filters.size() == 2) {
auto leftFilter = filter.filters[0];
auto rightFilter = filter.filters[1];
std::string lowerKey;
std::string upperKey;
mbgl::Value lowerBound;
mbgl::Value upperBound;
if (leftFilter.is<mbgl::style::GreaterThanEqualsFilter>()) {
lowerKey = leftFilter.get<mbgl::style::GreaterThanEqualsFilter>().key;
lowerBound = leftFilter.get<mbgl::style::GreaterThanEqualsFilter>().value;
} else if (rightFilter.is<mbgl::style::GreaterThanEqualsFilter>()) {
lowerKey = rightFilter.get<mbgl::style::GreaterThanEqualsFilter>().key;
lowerBound = rightFilter.get<mbgl::style::GreaterThanEqualsFilter>().value;
}
if (leftFilter.is<mbgl::style::LessThanEqualsFilter>()) {
upperKey = leftFilter.get<mbgl::style::LessThanEqualsFilter>().key;
upperBound = leftFilter.get<mbgl::style::LessThanEqualsFilter>().value;
} else if (rightFilter.is<mbgl::style::LessThanEqualsFilter>()) {
upperKey = rightFilter.get<mbgl::style::LessThanEqualsFilter>().key;
upperBound = rightFilter.get<mbgl::style::LessThanEqualsFilter>().value;
}
if (!lowerBound.is<mbgl::NullValue>() && !upperBound.is<mbgl::NullValue>()
&& lowerKey == upperKey) {
return [NSPredicate predicateWithFormat:@"%K BETWEEN {%@, %@}",
@(lowerKey.c_str()),
mbgl::Value::visit(lowerBound, ValueEvaluator()),
mbgl::Value::visit(upperBound, ValueEvaluator())];
}
}
NSArray *subpredicates = getPredicates(filter.filters);
if (subpredicates.count) {
return [NSCompoundPredicate andPredicateWithSubpredicates:subpredicates];
}
return [NSPredicate predicateWithValue:YES];
}
NSPredicate *operator()(mbgl::style::NoneFilter filter) {
NSArray *subpredicates = getPredicates(filter.filters);
if (subpredicates.count > 1) {
NSCompoundPredicate *predicate = [NSCompoundPredicate orPredicateWithSubpredicates:subpredicates];
return [NSCompoundPredicate notPredicateWithSubpredicate:predicate];
} else if (subpredicates.count) {
return [NSCompoundPredicate notPredicateWithSubpredicate:subpredicates.firstObject];
} else {
return [NSPredicate predicateWithValue:YES];
}
}
NSPredicate *operator()(mbgl::style::HasFilter filter) {
return [NSPredicate predicateWithFormat:@"%K != nil", @(filter.key.c_str())];
}
NSPredicate *operator()(mbgl::style::NotHasFilter filter) {
return [NSPredicate predicateWithFormat:@"%K == nil", @(filter.key.c_str())];
}
NSPredicate *operator()(mbgl::style::HasIdentifierFilter filter) {
return [NSPredicate predicateWithFormat:@"%K != nil", @"$id"];
}
NSPredicate *operator()(mbgl::style::NotHasIdentifierFilter filter) {
return [NSPredicate predicateWithFormat:@"%K == nil", @"$id"];
}
NSPredicate *operator()(mbgl::style::ExpressionFilter filter) {
id jsonObject = MGLJSONObjectFromMBGLExpression(*filter.expression);
return [NSPredicate predicateWithMGLJSONObject:jsonObject];
}
};
@implementation NSPredicate (MGLPrivateAdditions)
- (mbgl::style::Filter)mgl_filter
@@ -224,8 +25,12 @@ public:
+ (instancetype)mgl_predicateWithFilter:(mbgl::style::Filter)filter
{
FilterEvaluator evaluator;
return mbgl::style::Filter::visit(filter, evaluator);
if (filter.expression) {
id jsonObject = MGLJSONObjectFromMBGLExpression(**filter.expression);
return [NSPredicate predicateWithMGLJSONObject:jsonObject];
} else {
return nil;
}
}
@end

View File

@@ -4,226 +4,12 @@
#import "NSPredicate+MGLPrivateAdditions.h"
#import "MGLValueEvaluator.h"
namespace mbgl {
namespace style {
bool operator!=(const Filter &a, const Filter &b) {
return !(a == b);
}
}
}
#define MGLAssertEqualFilters(actual, expected, ...) \
XCTAssertTrue(actual.is<__typeof__(expected)>()); \
if (actual.is<__typeof__(expected)>()) { \
XCTAssertEqual(actual.get<__typeof__(expected)>(), expected, __VA_ARGS__); \
}
@interface MGLPredicateTests : XCTestCase
@end
@implementation MGLPredicateTests
- (void)testPredication {
XCTAssertNil([NSPredicate mgl_predicateWithFilter:mbgl::style::NullFilter()]);
{
mbgl::style::EqualsFilter filter = { .key = "a", .value = std::string("b") };
XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:filter], [NSPredicate predicateWithFormat:@"a = 'b'"]);
}
{
mbgl::style::TypeEqualsFilter filter = { .value = mbgl::FeatureType::Point };
NSPredicate *expected = [NSPredicate predicateWithFormat:@"%K = 'Point'", @"$type"];
XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:filter], expected);
}
{
mbgl::style::TypeEqualsFilter filter = { .value = mbgl::FeatureType::LineString };
NSPredicate *expected = [NSPredicate predicateWithFormat:@"%K = 'LineString'", @"$type"];
XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:filter], expected);
}
{
mbgl::style::TypeEqualsFilter filter = { .value = mbgl::FeatureType::Polygon };
NSPredicate *expected = [NSPredicate predicateWithFormat:@"%K = 'Polygon'", @"$type"];
XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:filter], expected);
}
{
mbgl::style::IdentifierEqualsFilter filter = { .value = UINT64_C(67086180) };
NSPredicate *expected = [NSPredicate predicateWithFormat:@"%K = 67086180", @"$id"];
XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:filter], expected);
}
{
mbgl::style::NotHasIdentifierFilter filter;
NSPredicate *expected = [NSPredicate predicateWithFormat:@"%K = nil", @"$id"];
XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:filter], expected);
}
{
mbgl::style::TypeEqualsFilter filter = { .value = mbgl::FeatureType::Unknown };
XCTAssertThrowsSpecificNamed([NSPredicate mgl_predicateWithFilter:filter], NSException, NSInternalInconsistencyException);
}
{
mbgl::style::NotHasFilter filter = { .key = "a" };
XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:filter], [NSPredicate predicateWithFormat:@"a = nil"]);
}
{
mbgl::style::NotEqualsFilter filter = { .key = "a", .value = std::string("b") };
XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:filter], [NSPredicate predicateWithFormat:@"a != 'b'"]);
}
{
mbgl::style::TypeNotEqualsFilter filter = { .value = mbgl::FeatureType::Point };
NSPredicate *expected = [NSPredicate predicateWithFormat:@"%K != 'Point'", @"$type"];
XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:filter], expected);
}
{
mbgl::style::IdentifierNotEqualsFilter filter = { .value = UINT64_C(67086180) };
NSPredicate *expected = [NSPredicate predicateWithFormat:@"%K != 67086180", @"$id"];
XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:filter], expected);
}
{
mbgl::style::HasIdentifierFilter filter;
NSPredicate *expected = [NSPredicate predicateWithFormat:@"%K != nil", @"$id"];
XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:filter], expected);
}
{
mbgl::style::HasFilter filter = { .key = "a" };
XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:filter], [NSPredicate predicateWithFormat:@"a != nil"]);
}
{
mbgl::style::LessThanFilter filter = { .key = "a", .value = std::string("b") };
XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:filter], [NSPredicate predicateWithFormat:@"a < 'b'"]);
}
{
mbgl::style::LessThanEqualsFilter filter = { .key = "a", .value = std::string("b") };
XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:filter], [NSPredicate predicateWithFormat:@"a <= 'b'"]);
}
{
mbgl::style::GreaterThanFilter filter = { .key = "a", .value = std::string("b") };
XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:filter], [NSPredicate predicateWithFormat:@"a > 'b'"]);
}
{
mbgl::style::GreaterThanEqualsFilter filter = { .key = "a", .value = std::string("b") };
XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:filter], [NSPredicate predicateWithFormat:@"a >= 'b'"]);
}
{
mbgl::style::AllFilter filter = {
.filters = {
mbgl::style::GreaterThanEqualsFilter { .key = "a", .value = std::string("b") },
mbgl::style::LessThanEqualsFilter { .key = "a", .value = std::string("z") },
},
};
XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:filter], [NSPredicate predicateWithFormat:@"a BETWEEN {'b', 'z'}"]);
}
{
mbgl::style::AllFilter filter = {
.filters = {
mbgl::style::LessThanEqualsFilter { .key = "a", .value = std::string("z") },
mbgl::style::GreaterThanEqualsFilter { .key = "a", .value = std::string("b") },
},
};
XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:filter], [NSPredicate predicateWithFormat:@"a BETWEEN {'b', 'z'}"]);
}
{
mbgl::style::InFilter filter = { .key = "a", .values = { std::string("b"), std::string("c") } };
XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:filter].predicateFormat, [NSPredicate predicateWithFormat:@"a IN {'b', 'c'}"].predicateFormat);
}
{
mbgl::style::TypeInFilter filter = { .values = { mbgl::FeatureType::LineString, mbgl::FeatureType::Polygon } };
NSPredicate *expected = [NSPredicate predicateWithFormat:@"%K IN {'LineString', 'Polygon'}", @"$type"];
XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:filter].predicateFormat, expected.predicateFormat);
}
{
mbgl::style::IdentifierInFilter filter = { .values = { UINT64_C(67086180), UINT64_C(3709678893), UINT64_C(3352016856), UINT64_C(4189833989) } };
NSPredicate *expected = [NSPredicate predicateWithFormat:@"%K IN {67086180, 3709678893, 3352016856, 4189833989}", @"$id"];
XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:filter], expected);
}
{
mbgl::style::NotInFilter filter = { .key = "a", .values = { std::string("b"), std::string("c") } };
XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:filter].predicateFormat, [NSPredicate predicateWithFormat:@"NOT a IN {'b', 'c'}"].predicateFormat);
}
{
mbgl::style::TypeNotInFilter filter = { .values = { mbgl::FeatureType::LineString, mbgl::FeatureType::Polygon } };
NSPredicate *expected = [NSPredicate predicateWithFormat:@"NOT %K IN {'LineString', 'Polygon'}", @"$type"];
XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:filter].predicateFormat, expected.predicateFormat);
}
{
mbgl::style::IdentifierNotInFilter filter = { .values = { UINT64_C(67086180), UINT64_C(3709678893), UINT64_C(3352016856), UINT64_C(4189833989) } };
NSPredicate *expected = [NSPredicate predicateWithFormat:@"NOT %K IN {67086180, 3709678893, 3352016856, 4189833989}", @"$id"];
XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:filter], expected);
}
{
mbgl::style::AllFilter filter;
XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:filter], [NSPredicate predicateWithValue:YES]);
}
{
mbgl::style::AllFilter filter = {
.filters = {
mbgl::style::EqualsFilter { .key = "a", .value = std::string("b") },
mbgl::style::EqualsFilter { .key = "c", .value = std::string("d") },
},
};
XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:filter], [NSPredicate predicateWithFormat:@"a == 'b' AND c == 'd'"]);
}
{
mbgl::style::AnyFilter filter;
XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:filter], [NSPredicate predicateWithValue:NO]);
}
{
mbgl::style::AnyFilter filter = {
.filters = {
mbgl::style::EqualsFilter { .key = "a", .value = std::string("b") },
mbgl::style::EqualsFilter { .key = "c", .value = std::string("d") },
},
};
XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:filter], [NSPredicate predicateWithFormat:@"a == 'b' OR c == 'd'"]);
}
{
mbgl::style::NoneFilter filter;
XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:filter], [NSPredicate predicateWithValue:YES]);
}
{
mbgl::style::NoneFilter filter = {
.filters = {
mbgl::style::EqualsFilter { .key = "a", .value = std::string("b") },
mbgl::style::EqualsFilter { .key = "c", .value = std::string("d") },
},
};
XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:filter], [NSPredicate predicateWithFormat:@"NOT(a == 'b' OR c == 'd')"]);
}
}
- (void)testUnsupportedFilterPredicates {
XCTAssertThrowsSpecificNamed([NSPredicate predicateWithFormat:@"1 == 2"].mgl_filter, NSException, NSInvalidArgumentException);
XCTAssertThrowsSpecificNamed([NSPredicate predicateWithFormat:@"1 == 1"].mgl_filter, NSException, NSInvalidArgumentException);
XCTAssertThrowsSpecificNamed([NSPredicate predicateWithValue:YES].mgl_filter, NSException, NSInvalidArgumentException);
XCTAssertThrowsSpecificNamed([NSPredicate predicateWithValue:NO].mgl_filter, NSException, NSInvalidArgumentException);
XCTAssertThrowsSpecificNamed([NSPredicate predicateWithFormat:@"a BEGINSWITH 'L'"].mgl_filter, NSException, NSInvalidArgumentException);
XCTAssertThrowsSpecificNamed([NSPredicate predicateWithFormat:@"a ENDSWITH 'itude'"].mgl_filter, NSException, NSInvalidArgumentException);
XCTAssertThrowsSpecificNamed([NSPredicate predicateWithFormat:@"a LIKE 'glob?trotter'"].mgl_filter, NSException, NSInvalidArgumentException);

View File

@@ -7,6 +7,8 @@
#include <mbgl/style/image.hpp>
#include <mbgl/style/transition_options.hpp>
#include <mbgl/style/layers/fill_extrusion_layer.hpp>
#include <mbgl/style/expression/compound_expression.hpp>
#include <mbgl/style/expression/literal.hpp>
#include <mbgl/util/logging.hpp>
#include <mbgl/util/platform.hpp>
#include <mbgl/util/string.hpp>
@@ -619,6 +621,9 @@ void GLFWView::onDidFinishLoadingStyle() {
}
void GLFWView::toggle3DExtrusions(bool visible) {
using namespace mbgl::style;
using namespace mbgl::style::expression;
show3DExtrusions = visible;
// Satellite-only style does not contain building extrusions data.
@@ -634,7 +639,9 @@ void GLFWView::toggle3DExtrusions(bool visible) {
auto extrusionLayer = std::make_unique<mbgl::style::FillExtrusionLayer>("3d-buildings", "composite");
extrusionLayer->setSourceLayer("building");
extrusionLayer->setMinZoom(15.0f);
extrusionLayer->setFilter(mbgl::style::EqualsFilter { "extrude", { std::string("true") } });
ParsingContext parsingContext;
extrusionLayer->setFilter(Filter(createCompoundExpression("filter-==", createLiteral("extrude"), createLiteral("true"), parsingContext)));
auto colorFn = mbgl::style::SourceFunction<mbgl::Color> { "height",
mbgl::style::ExponentialStops<mbgl::Color> {

View File

@@ -7,7 +7,6 @@
#include <mbgl/util/math.hpp>
#include <mbgl/math/minmax.hpp>
#include <mbgl/style/filter.hpp>
#include <mbgl/style/filter_evaluator.hpp>
#include <mbgl/tile/tile_id.hpp>
#include <mapbox/geometry/envelope.hpp>

View File

@@ -2,7 +2,6 @@
#include <mbgl/layout/merge_lines.hpp>
#include <mbgl/layout/clip_lines.hpp>
#include <mbgl/renderer/buckets/symbol_bucket.hpp>
#include <mbgl/style/filter_evaluator.hpp>
#include <mbgl/renderer/bucket_parameters.hpp>
#include <mbgl/renderer/layers/render_symbol_layer.hpp>
#include <mbgl/renderer/image_atlas.hpp>

View File

@@ -1,17 +1,39 @@
#include <mbgl/style/conversion/filter.hpp>
#include <mbgl/style/expression/literal.hpp>
#include <mbgl/util/geometry.hpp>
#include <mbgl/style/expression/expression.hpp>
#include <mbgl/style/expression/type.hpp>
#include <mbgl/style/expression/parsing_context.hpp>
#include <mbgl/style/expression/compound_expression.hpp>
#include <mbgl/style/expression/boolean_operator.hpp>
namespace mbgl {
namespace style {
namespace conversion {
using GeometryValue = mapbox::geometry::value;
using namespace mbgl::style::expression;
static bool isExpression(const Convertible& filter);
std::unique_ptr<Expression> convertLegacyFilter(const Convertible& values, Error& error);
optional<Filter> Converter<Filter>::operator()(const Convertible& value, Error& error) const {
if (isExpression(value)) {
ParsingContext parsingContext(type::Boolean);
ParseResult parseResult = parsingContext.parseExpression(value);
if (!parseResult) {
error = { parsingContext.getCombinedErrors() };
return {};
} else {
return { Filter(std::move(parseResult)) };
}
} else {
std::unique_ptr<Expression> expression = convertLegacyFilter(value, error);
if (!expression) return {};
return Filter(optional<std::unique_ptr<Expression>>(std::move(expression)));
}
}
// This is a port from https://github.com/mapbox/mapbox-gl-js/blob/master/src/style-spec/feature_filter/index.js
static bool isExpressionFilter(const Convertible& filter) {
bool isExpression(const Convertible& filter) {
if (!isArray(filter) || arrayLength(filter) == 0) {
return false;
}
@@ -20,7 +42,7 @@ static bool isExpressionFilter(const Convertible& filter) {
if (!op) {
return false;
} else if (*op == "has") {
if (arrayLength(filter) < 2) return false;
optional<std::string> operand = toString(arrayMember(filter, 1));
@@ -35,7 +57,7 @@ static bool isExpressionFilter(const Convertible& filter) {
} else if (*op == "any" || *op == "all") {
for (std::size_t i = 1; i < arrayLength(filter); i++) {
Convertible f = arrayMember(filter, i);
if (!isExpressionFilter(f) && !toBool(f)) {
if (!isExpression(f) && !toBool(f)) {
return false;
}
}
@@ -46,257 +68,136 @@ static bool isExpressionFilter(const Convertible& filter) {
}
}
static optional<GeometryValue> normalizeValue(const optional<GeometryValue>& value, Error& error) {
if (!value) {
error = { "filter expression value must be a boolean, number, or string" };
return {};
std::unique_ptr<Expression> createExpression(std::string op, std::vector<std::unique_ptr<Expression>> args, Error& error) {
if (op == "any") {
return std::make_unique<Any>(std::move(args));
} else if (op == "all") {
return std::make_unique<All>(std::move(args));
} else {
return *value;
}
}
static optional<FeatureType> toFeatureType(const Convertible& value, Error& error) {
optional<std::string> type = toString(value);
if (!type) {
error = { "value for $type filter must be a string" };
return {};
} else if (*type == "Point") {
return FeatureType::Point;
} else if (*type == "LineString") {
return FeatureType::LineString;
} else if (*type == "Polygon") {
return FeatureType::Polygon;
} else {
error = { "value for $type filter must be Point, LineString, or Polygon" };
return {};
}
}
static optional<FeatureIdentifier> toFeatureIdentifier(const Convertible& value, Error& error) {
optional<GeometryValue> identifier = toValue(value);
if (!identifier) {
error = { "filter expression value must be a boolean, number, or string" };
return {};
} else {
return (*identifier).match(
[] (uint64_t t) -> optional<FeatureIdentifier> { return { t }; },
[] ( int64_t t) -> optional<FeatureIdentifier> { return { t }; },
[] ( double t) -> optional<FeatureIdentifier> { return { t }; },
[] (const std::string& t) -> optional<FeatureIdentifier> { return { t }; },
[&] (const auto&) -> optional<FeatureIdentifier> {
error = { "filter expression value must be a boolean, number, or string" };
return {};
});
}
}
template <class FilterType, class IdentifierFilterType>
optional<Filter> convertUnaryFilter(const Convertible& value, Error& error) {
if (arrayLength(value) < 2) {
error = { "filter expression must have 2 elements" };
return {};
}
optional<std::string> key = toString(arrayMember(value, 1));
if (!key) {
error = { "filter expression key must be a string" };
return {};
}
if (*key == "$id") {
return { IdentifierFilterType {} };
} else {
return { FilterType { *key } };
}
}
template <class FilterType, class TypeFilterType, class IdentifierFilterType>
optional<Filter> convertEqualityFilter(const Convertible& value, Error& error) {
if (arrayLength(value) < 3) {
error = { "filter expression must have 3 elements" };
return {};
}
optional<std::string> key = toString(arrayMember(value, 1));
if (!key) {
error = { "filter expression key must be a string" };
return {};
}
if (*key == "$type") {
optional<FeatureType> filterValue = toFeatureType(arrayMember(value, 2), error);
if (!filterValue) {
ParsingContext parsingContext(type::Boolean);
ParseResult parseResult = createCompoundExpression(op, std::move(args), parsingContext);
if (!parseResult) {
error = { parsingContext.getCombinedErrors() };
return {};
} else {
return std::move(*parseResult);
}
}
}
return { TypeFilterType { *filterValue } };
} else if (*key == "$id") {
optional<FeatureIdentifier> filterValue = toFeatureIdentifier(arrayMember(value, 2), error);
if (!filterValue) {
return {};
}
return { IdentifierFilterType { *filterValue } };
std::unique_ptr<Expression> createExpression(std::string op, std::unique_ptr<Expression> expression, Error& error) {
std::vector<std::unique_ptr<Expression>> args;
args.push_back(std::move(expression));
return createExpression(op, std::move(args), error);
}
std::unique_ptr<Expression> convertLiteral(const Convertible& convertible, Error& error) {
ParsingContext parsingContext;
ParseResult parseResult = Literal::parse(convertible, parsingContext);
if (parseResult) {
return std::move(*parseResult);
} else {
optional<GeometryValue> filterValue = normalizeValue(toValue(arrayMember(value, 2)), error);
if (!filterValue) {
return {};
}
return { FilterType { *key, *filterValue } };
error = { parsingContext.getCombinedErrors() };
return {};
}
}
template <class FilterType>
optional<Filter> convertBinaryFilter(const Convertible& value, Error& error) {
if (arrayLength(value) < 3) {
error = { "filter expression must have 3 elements" };
return {};
std::vector<std::unique_ptr<Expression>> convertLiteralArray(const Convertible &input, Error& error, std::size_t startIndex = 0) {
std::vector<std::unique_ptr<Expression>> output;
for (std::size_t i = startIndex; i < arrayLength(input); i++) {
output.push_back(convertLiteral(arrayMember(input, i), error));
}
optional<std::string> key = toString(arrayMember(value, 1));
if (!key) {
error = { "filter expression key must be a string" };
return {};
}
optional<GeometryValue> filterValue = normalizeValue(toValue(arrayMember(value, 2)), error);
if (!filterValue) {
return {};
}
return { FilterType { *key, *filterValue } };
return output;
}
template <class FilterType, class TypeFilterType, class IdentifierFilterType>
optional<Filter> convertSetFilter(const Convertible& value, Error& error) {
if (arrayLength(value) < 2) {
error = { "filter expression must at least 2 elements" };
std::unique_ptr<Expression> convertLegacyComparisonFilter(const Convertible& values, Error& error, optional<std::string> opOverride = {}) {
optional<std::string> op = opOverride ? opOverride : toString(arrayMember(values, 0));
optional<std::string> property = toString(arrayMember(values, 1));
if (!property) {
error = { "filter property must be a string" };
return {};
}
optional<std::string> key = toString(arrayMember(value, 1));
if (!key) {
error = { "filter expression key must be a string" };
return {};
}
if (*key == "$type") {
std::vector<FeatureType> values;
for (std::size_t i = 2; i < arrayLength(value); ++i) {
optional<FeatureType> filterValue = toFeatureType(arrayMember(value, i), error);
if (!filterValue) {
return {};
}
values.push_back(*filterValue);
}
return { TypeFilterType { std::move(values) } };
} else if (*key == "$id") {
std::vector<FeatureIdentifier> values;
for (std::size_t i = 2; i < arrayLength(value); ++i) {
optional<FeatureIdentifier> filterValue = toFeatureIdentifier(arrayMember(value, i), error);
if (!filterValue) {
return {};
}
values.push_back(*filterValue);
}
return { IdentifierFilterType { std::move(values) } };
} else if (*property == "$type") {
return createExpression("filter-type-" + *op, convertLiteralArray(values, error, 2), error);
} else if (*property == "$id") {
return createExpression("filter-id-" + *op, convertLiteralArray(values, error, 2), error);
} else {
std::vector<GeometryValue> values;
for (std::size_t i = 2; i < arrayLength(value); ++i) {
optional<GeometryValue> filterValue = normalizeValue(toValue(arrayMember(value, i)), error);
if (!filterValue) {
return {};
}
values.push_back(*filterValue);
}
return { FilterType { *key, std::move(values) } };
return createExpression("filter-" + *op, convertLiteralArray(values, error, 1), error);
}
}
template <class FilterType>
optional<Filter> convertCompoundFilter(const Convertible& value, Error& error) {
std::vector<Filter> filters;
for (std::size_t i = 1; i < arrayLength(value); ++i) {
optional<Filter> element = convert<Filter>(arrayMember(value, i), error);
if (!element) {
return {};
}
filters.push_back(*element);
}
return { FilterType { std::move(filters) } };
}
optional<Filter> convertExpressionFilter(const Convertible& value, Error& error) {
expression::ParsingContext ctx(expression::type::Boolean);
expression::ParseResult expression = ctx.parseExpression(value);
if (!expression) {
error = { ctx.getCombinedErrors() };
std::unique_ptr<Expression> convertLegacyHasFilter(const Convertible& values, Error& error) {
optional<std::string> property = toString(arrayMember(values, 1));
if (!property) {
error = { "filter property must be a string" };
return {};
} else if (*property == "$type") {
return std::make_unique<Literal>(true);
} else if (*property == "$id") {
return createExpression("filter-has-id", std::vector<std::unique_ptr<Expression>>(), error);
} else {
return createExpression("filter-has", std::make_unique<Literal>(*property), error);
}
return { ExpressionFilter { std::move(*expression) } };
}
optional<Filter> Converter<Filter>::operator()(const Convertible& value, Error& error) const {
if (isExpressionFilter(value)) {
return convertExpressionFilter(value, error);
std::unique_ptr<Expression> convertLegacyInFilter(const Convertible& values, Error& error) {
optional<std::string> property = toString(arrayMember(values, 1));
if (!property) {
error = { "filter property must be a string" };
return {};
} else if (arrayLength(values) == 0) {
return std::make_unique<Literal>(false);
} else if (*property == "$type") {
return createExpression("filter-type-in", convertLiteralArray(values, error, 2), error);
} else if (*property == "$id") {
return createExpression("filter-id-in", convertLiteralArray(values, error, 2), error);
} else {
return createExpression("filter-in", convertLiteralArray(values, error, 1), error);
}
}
std::vector<std::unique_ptr<Expression>> convertLegacyFilterArray(const Convertible &input, Error& error, std::size_t startIndex = 0) {
std::vector<std::unique_ptr<Expression>> output;
for (std::size_t i = startIndex; i < arrayLength(input); i++) {
output.push_back(convertLegacyFilter(arrayMember(input, i), error));
}
return output;
}
std::unique_ptr<Expression> convertLegacyFilter(const Convertible& values, Error& error) {
if (isUndefined(values)) {
return std::make_unique<Literal>(true);
}
if (!isArray(value)) {
error = { "filter expression must be an array" };
return {};
}
optional<std::string> op = toString(arrayMember(values, 0));
if (arrayLength(value) < 1) {
error = { "filter expression must have at least 1 element" };
return {};
}
optional<std::string> op = toString(arrayMember(value, 0));
if (!op) {
error = { "filter operator must be a string" };
return {};
} else if (arrayLength(values) <= 1) {
return std::make_unique<Literal>(*op != "any");
} else {
return (
*op == "==" ||
*op == "<" ||
*op == ">" ||
*op == "<=" ||
*op == ">=" ? convertLegacyComparisonFilter(values, error) :
*op == "!=" ? createExpression("!", convertLegacyComparisonFilter(values, error, {"=="}), error) :
*op == "any" ? createExpression("any", convertLegacyFilterArray(values, error, 1), error) :
*op == "all" ? createExpression("all", convertLegacyFilterArray(values, error, 1), error) :
*op == "none" ? createExpression("!", createExpression("any", convertLegacyFilterArray(values, error, 1), error), error) :
*op == "in" ? convertLegacyInFilter(values, error) :
*op == "!in" ? createExpression("!", convertLegacyInFilter(values, error), error) :
*op == "has" ? convertLegacyHasFilter(values, error) :
*op == "!has" ? createExpression("!", convertLegacyHasFilter(values, error), error) :
std::make_unique<Literal>(true)
);
}
if (*op == "==") {
return convertEqualityFilter<EqualsFilter, TypeEqualsFilter, IdentifierEqualsFilter>(value, error);
} else if (*op == "!=") {
return convertEqualityFilter<NotEqualsFilter, TypeNotEqualsFilter, IdentifierNotEqualsFilter>(value, error);
} else if (*op == ">") {
return convertBinaryFilter<GreaterThanFilter>(value, error);
} else if (*op == ">=") {
return convertBinaryFilter<GreaterThanEqualsFilter>(value, error);
} else if (*op == "<") {
return convertBinaryFilter<LessThanFilter>(value, error);
} else if (*op == "<=") {
return convertBinaryFilter<LessThanEqualsFilter>(value, error);
} else if (*op == "in") {
return convertSetFilter<InFilter, TypeInFilter, IdentifierInFilter>(value, error);
} else if (*op == "!in") {
return convertSetFilter<NotInFilter, TypeNotInFilter, IdentifierNotInFilter>(value, error);
} else if (*op == "all") {
return convertCompoundFilter<AllFilter>(value, error);
} else if (*op == "any") {
return convertCompoundFilter<AnyFilter>(value, error);
} else if (*op == "none") {
return convertCompoundFilter<NoneFilter>(value, error);
} else if (*op == "has") {
return convertUnaryFilter<HasFilter, HasIdentifierFilter>(value, error);
} else if (*op == "!has") {
return convertUnaryFilter<NotHasFilter, NotHasIdentifierFilter>(value, error);
}
error = { R"(filter operator must be one of "==", "!=", ">", ">=", "<", "<=", "in", "!in", "all", "any", "none", "has", or "!has")" };
return {};
}
} // namespace conversion

View File

@@ -126,162 +126,9 @@ void stringify(Writer& writer, const FeatureIdentifier& id) {
}
template <class Writer>
class StringifyFilter {
public:
Writer& writer;
void operator()(const NullFilter&) {
writer.Null();
}
void operator()(const EqualsFilter& f) {
stringifyBinaryFilter(f, "==");
}
void operator()(const NotEqualsFilter& f) {
stringifyBinaryFilter(f, "!=");
}
void operator()(const LessThanFilter& f) {
stringifyBinaryFilter(f, "<");
}
void operator()(const LessThanEqualsFilter& f) {
stringifyBinaryFilter(f, "<=");
}
void operator()(const GreaterThanFilter& f) {
stringifyBinaryFilter(f, ">");
}
void operator()(const GreaterThanEqualsFilter& f) {
stringifyBinaryFilter(f, ">=");
}
void operator()(const InFilter& f) {
stringifySetFilter(f, "in");
}
void operator()(const NotInFilter& f) {
stringifySetFilter(f, "!in");
}
void operator()(const AllFilter& f) {
stringifyCompoundFilter(f, "all");
}
void operator()(const AnyFilter& f) {
stringifyCompoundFilter(f, "any");
}
void operator()(const NoneFilter& f) {
stringifyCompoundFilter(f, "none");
}
void operator()(const HasFilter& f) {
stringifyUnaryFilter("has", f.key);
}
void operator()(const NotHasFilter& f) {
stringifyUnaryFilter("!has", f.key);
}
void operator()(const TypeEqualsFilter& f) {
stringifyBinaryFilter(f, "==", "$type");
}
void operator()(const TypeNotEqualsFilter& f) {
stringifyBinaryFilter(f, "!=", "$type");
}
void operator()(const TypeInFilter& f) {
stringifySetFilter(f, "in", "$type");
}
void operator()(const TypeNotInFilter& f) {
stringifySetFilter(f, "!in", "$type");
}
void operator()(const IdentifierEqualsFilter& f) {
stringifyBinaryFilter(f, "==", "$id");
}
void operator()(const IdentifierNotEqualsFilter& f) {
stringifyBinaryFilter(f, "!=", "$id");
}
void operator()(const IdentifierInFilter& f) {
stringifySetFilter(f, "in", "$id");
}
void operator()(const IdentifierNotInFilter& f) {
stringifySetFilter(f, "!in", "$id");
}
void operator()(const HasIdentifierFilter&) {
stringifyUnaryFilter("has", "$id");
}
void operator()(const NotHasIdentifierFilter&) {
stringifyUnaryFilter("!has", "$id");
}
void operator()(const ExpressionFilter& filter) {
stringify(writer, filter.expression->serialize());
}
private:
template <class F>
void stringifyBinaryFilter(const F& f, const char * op) {
stringifyBinaryFilter(f, op, f.key);
}
template <class F>
void stringifyBinaryFilter(const F& f, const char * op, const std::string& key) {
writer.StartArray();
writer.String(op);
writer.String(key);
stringify(writer, f.value);
writer.EndArray();
}
template <class F>
void stringifySetFilter(const F& f, const char * op) {
stringifySetFilter(f, op, f.key);
}
template <class F>
void stringifySetFilter(const F& f, const char * op, const std::string& key) {
writer.StartArray();
writer.String(op);
writer.String(key);
for (const auto& value : f.values) {
stringify(writer, value);
}
writer.EndArray();
}
template <class F>
void stringifyCompoundFilter(const F& f, const char * op) {
writer.StartArray();
writer.String(op);
for (const auto& filter : f.filters) {
Filter::visit(filter, *this);
}
writer.EndArray();
}
void stringifyUnaryFilter(const char * op, const std::string& key) {
writer.StartArray();
writer.String(op);
writer.String(key);
writer.EndArray();
}
};
template <class Writer>
void stringify(Writer& writer, const Filter& f) {
Filter::visit(f, StringifyFilter<Writer> { writer });
void stringify(Writer& writer, const Filter& filter) {
if (!filter.expression) writer.Null();
else stringify(writer, (*filter.expression)->serialize());
}
template <class Writer>

View File

@@ -18,7 +18,7 @@ namespace detail {
The Signature<Fn> structs are wrappers around an "evaluate()" function whose
purpose is to extract the necessary Type data from the evaluate function's
type. There are three key (partial) specializations:
Signature<R (Params...)>:
Wraps a simple evaluate function (const T0&, const T1&, ...) -> Result<U>
@@ -29,9 +29,9 @@ namespace detail {
Signature<R (const EvaluationContext&, Params...)>:
Wraps an evaluate function that needs to access the expression evaluation
parameters in addition to its subexpressions, i.e.,
(const EvaluationParams& const T0&, const T1&, ...) -> Result<U>. Needed
(const EvaluationParams&, const T0&, const T1&, ...) -> Result<U>. Needed
for expressions like ["zoom"], ["get", key], etc.
In each of the above evaluate signatures, T0, T1, etc. are the types of
the successfully evaluated subexpressions.
*/
@@ -42,7 +42,7 @@ struct Signature;
template <class R, class... Params>
struct Signature<R (Params...)> : SignatureBase {
using Args = std::array<std::unique_ptr<Expression>, sizeof...(Params)>;
Signature(R (*evaluate_)(Params...), std::string name_) :
SignatureBase(
valueTypeToExpressionType<std::decay_t<typename R::Value>>(),
@@ -54,7 +54,7 @@ struct Signature<R (Params...)> : SignatureBase {
EvaluationResult apply(const EvaluationContext& evaluationParameters, const Args& args) const {
return applyImpl(evaluationParameters, args, std::index_sequence_for<Params...>{});
}
std::unique_ptr<Expression> makeExpression(std::vector<std::unique_ptr<Expression>> args) const override {
typename Signature::Args argsArray;
std::copy_n(std::make_move_iterator(args.begin()), sizeof...(Params), argsArray.begin());
@@ -79,7 +79,7 @@ private:
template <class R, typename T>
struct Signature<R (const Varargs<T>&)> : SignatureBase {
using Args = std::vector<std::unique_ptr<Expression>>;
Signature(R (*evaluate_)(const Varargs<T>&), std::string name_) :
SignatureBase(
valueTypeToExpressionType<std::decay_t<typename R::Value>>(),
@@ -88,11 +88,11 @@ struct Signature<R (const Varargs<T>&)> : SignatureBase {
),
evaluate(evaluate_)
{}
std::unique_ptr<Expression> makeExpression(std::vector<std::unique_ptr<Expression>> args) const override {
return std::make_unique<CompoundExpression<Signature>>(name, *this, std::move(args));
};
EvaluationResult apply(const EvaluationContext& evaluationParameters, const Args& args) const {
Varargs<T> evaluated;
evaluated.reserve(args.size());
@@ -114,7 +114,7 @@ struct Signature<R (const Varargs<T>&)> : SignatureBase {
template <class R, class... Params>
struct Signature<R (const EvaluationContext&, Params...)> : SignatureBase {
using Args = std::array<std::unique_ptr<Expression>, sizeof...(Params)>;
Signature(R (*evaluate_)(const EvaluationContext&, Params...), std::string name_) :
SignatureBase(
valueTypeToExpressionType<std::decay_t<typename R::Value>>(),
@@ -123,17 +123,17 @@ struct Signature<R (const EvaluationContext&, Params...)> : SignatureBase {
),
evaluate(evaluate_)
{}
std::unique_ptr<Expression> makeExpression(std::vector<std::unique_ptr<Expression>> args) const override {
typename Signature::Args argsArray;
std::copy_n(std::make_move_iterator(args.begin()), sizeof...(Params), argsArray.begin());
return std::make_unique<CompoundExpression<Signature>>(name, *this, std::move(argsArray));
}
EvaluationResult apply(const EvaluationContext& evaluationParameters, const Args& args) const {
return applyImpl(evaluationParameters, args, std::index_sequence_for<Params...>{});
}
private:
template <std::size_t ...I>
EvaluationResult applyImpl(const EvaluationContext& evaluationParameters, const Args& args, std::index_sequence<I...>) const {
@@ -149,6 +149,41 @@ private:
R (*evaluate)(const EvaluationContext&, Params...);
};
// Evaluate function needing EvaluationContext and Varargs
// (const EvaluationContext&, const Varargs<T>&) -> Result<U>
template <class R, typename T>
struct Signature<R (const EvaluationContext&, const Varargs<T>&)> : SignatureBase {
using Args = std::vector<std::unique_ptr<Expression>>;
Signature(R (*evaluate_)(const EvaluationContext&, const Varargs<T>&), std::string name_) :
SignatureBase(
valueTypeToExpressionType<std::decay_t<typename R::Value>>(),
VarargsType { valueTypeToExpressionType<T>() },
std::move(name_)
),
evaluate(evaluate_)
{}
std::unique_ptr<Expression> makeExpression(std::vector<std::unique_ptr<Expression>> args) const override {
return std::make_unique<CompoundExpression<Signature>>(name, *this, std::move(args));
};
EvaluationResult apply(const EvaluationContext& evaluationParameters, const Args& args) const {
Varargs<T> evaluated;
evaluated.reserve(args.size());
for (const auto& arg : args) {
const EvaluationResult evaluatedArg = arg->evaluate(evaluationParameters);
if(!evaluatedArg) return evaluatedArg.error();
evaluated.push_back(*fromExpressionValue<std::decay_t<T>>(*evaluatedArg));
}
const R value = evaluate(evaluationParameters, evaluated);
if (!value) return value.error();
return *value;
}
R (*evaluate)(const EvaluationContext&, const Varargs<T>&);
};
// Machinery to pull out function types from class methods, lambdas, etc.
template <class R, class... Params>
@@ -180,18 +215,92 @@ static std::unique_ptr<detail::SignatureBase> makeSignature(Fn evaluateFunction,
return std::make_unique<detail::Signature<Fn>>(evaluateFunction, std::move(name));
}
Value featureIdAsExpressionValue(EvaluationContext params) {
assert(params.feature);
auto id = params.feature->getID();
if (!id) return Null;
return id->match([](const auto& idid) {
return toExpressionValue(mbgl::Value(idid));
});
};
optional<Value> featurePropertyAsExpressionValue(EvaluationContext params, const std::string& key) {
assert(params.feature);
auto property = params.feature->getValue(key);
return property ? toExpressionValue(*property) : optional<Value>();
};
optional<std::string> featureTypeAsString(FeatureType type) {
switch(type) {
case FeatureType::Point:
return optional<std::string>("Point");
case FeatureType::LineString:
return optional<std::string>("LineString");
case FeatureType::Polygon:
return optional<std::string>("Polygon");
case FeatureType::Unknown:
return optional<std::string>("Unknown");
default:
return {};
}
};
optional<double> featurePropertyAsDouble(EvaluationContext params, const std::string& key) {
assert(params.feature);
auto property = params.feature->getValue(key);
if (!property) return {};
return property->match(
[](double value) { return value; },
[](uint64_t value) { return optional<double>(static_cast<double>(value)); },
[](int64_t value) { return optional<double>(static_cast<double>(value)); },
[](auto) { return optional<double>(); }
);
};
optional<std::string> featurePropertyAsString(EvaluationContext params, const std::string& key) {
assert(params.feature);
auto property = params.feature->getValue(key);
if (!property) return {};
return property->match(
[](std::string value) { return value; },
[](auto) { return optional<std::string>(); }
);
};
optional<double> featureIdAsDouble(EvaluationContext params) {
assert(params.feature);
auto id = params.feature->getID();
if (!id) return optional<double>();
return id->match(
[](double value) { return value; },
[](uint64_t value) { return optional<double>(static_cast<double>(value)); },
[](int64_t value) { return optional<double>(static_cast<double>(value)); },
[](auto) { return optional<double>(); }
);
};
optional<std::string> featureIdAsString(EvaluationContext params) {
assert(params.feature);
auto id = params.feature->getID();
if (!id) return optional<std::string>();
return id->match(
[](std::string value) { return value; },
[](auto) { return optional<std::string>(); }
);
};
std::unordered_map<std::string, CompoundExpressionRegistry::Definition> initializeDefinitions() {
std::unordered_map<std::string, CompoundExpressionRegistry::Definition> definitions;
auto define = [&](std::string name, auto fn) {
definitions[name].push_back(makeSignature(fn, name));
};
define("e", []() -> Result<double> { return 2.718281828459045; });
define("pi", []() -> Result<double> { return 3.141592653589793; });
define("ln2", []() -> Result<double> { return 0.6931471805599453; });
define("typeof", [](const Value& v) -> Result<std::string> { return toString(typeOf(v)); });
define("to-string", [](const Value& value) -> Result<std::string> {
return value.match(
[](const Color& c) -> Result<std::string> { return c.stringify(); }, // avoid quoting
@@ -199,10 +308,10 @@ std::unordered_map<std::string, CompoundExpressionRegistry::Definition> initiali
[](const auto& v) -> Result<std::string> { return stringify(v); }
);
});
define("to-boolean", [](const Value& v) -> Result<bool> {
return v.match(
[&] (double f) { return (bool)f; },
[&] (double f) { return static_cast<bool>(f); },
[&] (const std::string& s) { return s.length() > 0; },
[&] (bool b) { return b; },
[&] (const NullValue&) { return false; },
@@ -212,10 +321,10 @@ std::unordered_map<std::string, CompoundExpressionRegistry::Definition> initiali
define("to-rgba", [](const Color& color) -> Result<std::array<double, 4>> {
return color.toArray();
});
define("rgba", rgba);
define("rgb", [](double r, double g, double b) { return rgba(r, g, b, 1.0f); });
define("zoom", [](const EvaluationContext& params) -> Result<double> {
if (!params.zoom) {
return EvaluationError {
@@ -224,7 +333,7 @@ std::unordered_map<std::string, CompoundExpressionRegistry::Definition> initiali
}
return *(params.zoom);
});
define("heatmap-density", [](const EvaluationContext& params) -> Result<double> {
if (!params.heatmapDensity) {
return EvaluationError {
@@ -240,7 +349,7 @@ std::unordered_map<std::string, CompoundExpressionRegistry::Definition> initiali
"Feature data is unavailable in the current evaluation context."
};
}
return params.feature->getValue(key) ? true : false;
});
define("has", [](const std::string& key, const std::unordered_map<std::string, Value>& object) -> Result<bool> {
@@ -266,7 +375,7 @@ std::unordered_map<std::string, CompoundExpressionRegistry::Definition> initiali
}
return object.at(key);
});
define("properties", [](const EvaluationContext& params) -> Result<std::unordered_map<std::string, Value>> {
if (!params.feature) {
return EvaluationError {
@@ -280,14 +389,14 @@ std::unordered_map<std::string, CompoundExpressionRegistry::Definition> initiali
}
return result;
});
define("geometry-type", [](const EvaluationContext& params) -> Result<std::string> {
if (!params.feature) {
return EvaluationError {
"Feature data is unavailable in the current evaluation context."
};
}
auto type = params.feature->getType();
if (type == FeatureType::Point) {
return "Point";
@@ -299,14 +408,14 @@ std::unordered_map<std::string, CompoundExpressionRegistry::Definition> initiali
return "Unknown";
}
});
define("id", [](const EvaluationContext& params) -> Result<Value> {
if (!params.feature) {
return EvaluationError {
"Feature data is unavailable in the current evaluation context."
};
}
auto id = params.feature->getID();
if (!id) {
return Null;
@@ -317,7 +426,7 @@ std::unordered_map<std::string, CompoundExpressionRegistry::Definition> initiali
}
);
});
define("+", [](const Varargs<double>& args) -> Result<double> {
double sum = 0.0f;
for (auto arg : args) {
@@ -347,7 +456,7 @@ std::unordered_map<std::string, CompoundExpressionRegistry::Definition> initiali
define("asin", [](double x) -> Result<double> { return asin(x); });
define("acos", [](double x) -> Result<double> { return acos(x); });
define("atan", [](double x) -> Result<double> { return atan(x); });
define("min", [](const Varargs<double>& args) -> Result<double> {
double result = std::numeric_limits<double>::infinity();
for (double arg : args) {
@@ -362,7 +471,7 @@ std::unordered_map<std::string, CompoundExpressionRegistry::Definition> initiali
}
return result;
});
define("round", [](double x) -> Result<double> { return std::round(x); });
define("floor", [](double x) -> Result<double> { return std::floor(x); });
define("ceil", [](double x) -> Result<double> { return std::ceil(x); });
@@ -376,9 +485,9 @@ std::unordered_map<std::string, CompoundExpressionRegistry::Definition> initiali
define("<", [](const std::string& lhs, const std::string& rhs) -> Result<bool> { return lhs < rhs; });
define("<=", [](double lhs, double rhs) -> Result<bool> { return lhs <= rhs; });
define("<=", [](const std::string& lhs, const std::string& rhs) -> Result<bool> { return lhs <= rhs; });
define("!", [](bool e) -> Result<bool> { return !e; });
define("upcase", [](const std::string& input) -> Result<std::string> {
return platform::uppercase(input);
});
@@ -395,7 +504,130 @@ std::unordered_map<std::string, CompoundExpressionRegistry::Definition> initiali
define("error", [](const std::string& input) -> Result<type::ErrorType> {
return EvaluationError { input };
});
// Legacy Filters
define("filter-==", [](const EvaluationContext& params, const std::string& key, const Value &lhs) -> Result<bool> {
const auto rhs = featurePropertyAsExpressionValue(params, key);
return rhs ? lhs == *rhs : false;
});
define("filter-id-==", [](const EvaluationContext& params, const Value &lhs) -> Result<bool> {
return lhs == featureIdAsExpressionValue(params);
});
define("filter-type-==", [](const EvaluationContext& params, const std::string &lhs) -> Result<bool> {
if (!params.feature) return false;
return featureTypeAsString(params.feature->getType()) == lhs;
});
define("filter-<", [](const EvaluationContext& params, const std::string& key, double lhs) -> Result<bool> {
auto rhs = featurePropertyAsDouble(params, key);
return rhs ? rhs < lhs : false;
});
define("filter-<", [](const EvaluationContext& params, const std::string& key, std::string lhs) -> Result<bool> {
auto rhs = featurePropertyAsString(params, key);
return rhs ? rhs < lhs : false;
});
define("filter-id-<", [](const EvaluationContext& params, double lhs) -> Result<bool> {
auto rhs = featureIdAsDouble(params);
return rhs ? rhs < lhs : false;
});
define("filter-id-<", [](const EvaluationContext& params, std::string lhs) -> Result<bool> {
auto rhs = featureIdAsString(params);
return rhs ? rhs < lhs : false;
});
define("filter->", [](const EvaluationContext& params, const std::string& key, double lhs) -> Result<bool> {
auto rhs = featurePropertyAsDouble(params, key);
return rhs ? rhs > lhs : false;
});
define("filter->", [](const EvaluationContext& params, const std::string& key, std::string lhs) -> Result<bool> {
auto rhs = featurePropertyAsString(params, key);
return rhs ? rhs > lhs : false;
});
define("filter-id->", [](const EvaluationContext& params, double lhs) -> Result<bool> {
auto rhs = featureIdAsDouble(params);
return rhs ? rhs > lhs : false;
});
define("filter-id->", [](const EvaluationContext& params, std::string lhs) -> Result<bool> {
auto rhs = featureIdAsString(params);
return rhs ? rhs > lhs : false;
});
define("filter-<=", [](const EvaluationContext& params, const std::string& key, double lhs) -> Result<bool> {
auto rhs = featurePropertyAsDouble(params, key);
return rhs ? rhs <= lhs : false;
});
define("filter-<=", [](const EvaluationContext& params, const std::string& key, std::string lhs) -> Result<bool> {
auto rhs = featurePropertyAsString(params, key);
return rhs ? rhs <= lhs : false;
});
define("filter-id-<=", [](const EvaluationContext& params, double lhs) -> Result<bool> {
auto rhs = featureIdAsDouble(params);
return rhs ? rhs <= lhs : false;
});
define("filter-id-<=", [](const EvaluationContext& params, std::string lhs) -> Result<bool> {
auto rhs = featureIdAsString(params);
return rhs ? rhs <= lhs : false;
});
define("filter->=", [](const EvaluationContext& params, const std::string& key, double lhs) -> Result<bool> {
auto rhs = featurePropertyAsDouble(params, key);
return rhs ? rhs >= lhs : false;
});
define("filter->=", [](const EvaluationContext& params, const std::string& key, std::string lhs) -> Result<bool> {
auto rhs = featurePropertyAsString(params, key);
return rhs ? rhs >= lhs : false;
});
define("filter-id->=", [](const EvaluationContext& params, double lhs) -> Result<bool> {
auto rhs = featureIdAsDouble(params);
return rhs ? rhs >= lhs : false;
});
define("filter-id->=", [](const EvaluationContext& params, std::string lhs) -> Result<bool> {
auto rhs = featureIdAsString(params);
return rhs ? rhs >= lhs : false;
});
define("filter-has", [](const EvaluationContext& params, const std::string& key) -> Result<bool> {
assert(params.feature);
return bool(params.feature->getValue(key));
});
define("filter-has-id", [](const EvaluationContext& params) -> Result<bool> {
assert(params.feature);
return bool(params.feature->getID());
});
define("filter-type-in", [](const EvaluationContext& params, const Varargs<std::string>& types) -> Result<bool> {
assert(params.feature);
optional<std::string> type = featureTypeAsString(params.feature->getType());
return std::find(types.begin(), types.end(), type) != types.end();
});
define("filter-id-in", [](const EvaluationContext& params, const Varargs<Value>& ids) -> Result<bool> {
auto id = featureIdAsExpressionValue(params);
return std::find(ids.begin(), ids.end(), id) != ids.end();
});
define("filter-in", [](const EvaluationContext& params, const Varargs<Value>& varargs) -> Result<bool> {
if (varargs.size() < 2) return false;
assert(varargs[0].is<std::string>());
auto value = featurePropertyAsExpressionValue(params, varargs[0].get<std::string>());
return value ? std::find(varargs.begin() + 1, varargs.end(), *value) != varargs.end() : false;
});
return definitions;
}
@@ -414,7 +646,7 @@ ParseResult parseCompoundExpression(const std::string name, const Convertible& v
return ParseResult();
}
const CompoundExpressionRegistry::Definition& definition = it->second;
auto length = arrayLength(value);
// Check if we have a single signature with the correct number of
@@ -440,14 +672,14 @@ ParseResult parseCompoundExpression(const std::string name, const Convertible& v
args.reserve(length - 1);
for (std::size_t i = 1; i < length; i++) {
optional<type::Type> expected;
if (singleMatchingSignature) {
expected = definition[*singleMatchingSignature]->params.match(
[](const VarargsType& varargs) { return varargs.type; },
[&](const std::vector<type::Type>& params_) { return params_[i - 1]; }
);
}
auto parsed = ctx.parse(arrayMember(value, i), i, expected);
if (!parsed) {
return parsed;
@@ -474,7 +706,7 @@ ParseResult createCompoundExpression(const Definition& definition,
for (const std::unique_ptr<detail::SignatureBase>& signature : definition) {
signatureContext.clearErrors();
if (signature->params.is<std::vector<type::Type>>()) {
const std::vector<type::Type>& params = signature->params.get<std::vector<type::Type>>();
if (params.size() != args.size()) {
@@ -502,12 +734,12 @@ ParseResult createCompoundExpression(const Definition& definition,
}
}
}
if (signatureContext.getErrors().size() == 0) {
return ParseResult(signature->makeExpression(std::move(args)));
}
}
if (definition.size() == 1) {
ctx.appendErrors(std::move(signatureContext));
} else {
@@ -540,10 +772,32 @@ ParseResult createCompoundExpression(const Definition& definition,
}
ctx.error("Expected arguments of type " + signatures + ", but found (" + actualTypes + ") instead.");
}
return ParseResult();
}
ParseResult createCompoundExpression(const std::string& name, ParsingContext& ctx) {
return createCompoundExpression(name, std::vector<std::unique_ptr<Expression>>(), ctx);
}
ParseResult createCompoundExpression(const std::string& name,
std::unique_ptr<Expression> arg1,
ParsingContext& ctx) {
std::vector<std::unique_ptr<Expression>> args;
args.push_back(std::move(arg1));
return createCompoundExpression(name, std::move(args), ctx);
}
ParseResult createCompoundExpression(const std::string& name,
std::unique_ptr<Expression> arg1,
std::unique_ptr<Expression> arg2,
ParsingContext& ctx) {
std::vector<std::unique_ptr<Expression>> args;
args.push_back(std::move(arg1));
args.push_back(std::move(arg2));
return createCompoundExpression(name, std::move(args), ctx);
}
} // namespace expression
} // namespace style
} // namespace mbgl

View File

@@ -109,6 +109,14 @@ mbgl::Value Literal::serialize() const {
return *fromExpressionValue<mbgl::Value>(value);
}
}
std::unique_ptr<Literal> createLiteral(const char* value) {
return createLiteral(std::string(value));
}
std::unique_ptr<Literal> createLiteral(Value value) {
return std::make_unique<Literal>(value);
}
} // namespace expression
} // namespace style

View File

@@ -1,12 +1,20 @@
#include <mbgl/style/filter.hpp>
#include <mbgl/style/filter_evaluator.hpp>
#include <mbgl/tile/geometry_tile_data.hpp>
namespace mbgl {
namespace style {
bool Filter::operator()(const expression::EvaluationContext &context) const {
return FilterBase::visit(*this, FilterEvaluator { context });
if (!this->expression) return true;
const expression::EvaluationResult result = (*this->expression)->evaluate(context);
if (result) {
const optional<bool> typed = expression::fromExpressionValue<bool>(*result);
return typed ? *typed : false;
} else {
return true;
}
}
} // namespace style

View File

@@ -1,225 +0,0 @@
#include <mbgl/style/filter.hpp>
#include <mbgl/style/filter_evaluator.hpp>
#include <mbgl/tile/geometry_tile_data.hpp>
namespace mbgl {
namespace style {
template <class Op>
struct Comparator {
const Op& op;
template <class T>
bool operator()(const T& lhs, const T& rhs) const {
return op(lhs, rhs);
}
template <class T0, class T1>
auto operator()(const T0& lhs, const T1& rhs) const
-> typename std::enable_if_t<std::is_arithmetic<T0>::value && !std::is_same<T0, bool>::value &&
std::is_arithmetic<T1>::value && !std::is_same<T1, bool>::value, bool> {
return op(double(lhs), double(rhs));
}
template <class T0, class T1>
auto operator()(const T0&, const T1&) const
-> typename std::enable_if_t<!std::is_arithmetic<T0>::value || std::is_same<T0, bool>::value ||
!std::is_arithmetic<T1>::value || std::is_same<T1, bool>::value, bool> {
return false;
}
bool operator()(const NullValue&,
const NullValue&) const {
// Should be unreachable; null is not currently allowed by the style specification.
assert(false);
return false;
}
bool operator()(const std::vector<Value>&,
const std::vector<Value>&) const {
// Should be unreachable; nested values are not currently allowed by the style specification.
assert(false);
return false;
}
bool operator()(const PropertyMap&,
const PropertyMap&) const {
// Should be unreachable; nested values are not currently allowed by the style specification.
assert(false);
return false;
}
};
template <class Op>
bool compare(const Value& lhs, const Value& rhs, const Op& op) {
return Value::binary_visit(lhs, rhs, Comparator<Op> { op });
}
bool equal(const Value& lhs, const Value& rhs) {
return compare(lhs, rhs, [] (const auto& lhs_, const auto& rhs_) { return lhs_ == rhs_; });
}
bool FilterEvaluator::operator()(const NullFilter&) const {
return true;
}
bool FilterEvaluator::operator()(const EqualsFilter& filter) const {
optional<Value> actual = context.feature->getValue(filter.key);
return actual && equal(*actual, filter.value);
}
bool FilterEvaluator::operator()(const NotEqualsFilter& filter) const {
optional<Value> actual = context.feature->getValue(filter.key);
return !actual || !equal(*actual, filter.value);
}
bool FilterEvaluator::operator()(const LessThanFilter& filter) const {
optional<Value> actual = context.feature->getValue(filter.key);
return actual && compare(*actual, filter.value, [] (const auto& lhs_, const auto& rhs_) { return lhs_ < rhs_; });
}
bool FilterEvaluator::operator()(const LessThanEqualsFilter& filter) const {
optional<Value> actual = context.feature->getValue(filter.key);
return actual && compare(*actual, filter.value, [] (const auto& lhs_, const auto& rhs_) { return lhs_ <= rhs_; });
}
bool FilterEvaluator::operator()(const GreaterThanFilter& filter) const {
optional<Value> actual = context.feature->getValue(filter.key);
return actual && compare(*actual, filter.value, [] (const auto& lhs_, const auto& rhs_) { return lhs_ > rhs_; });
}
bool FilterEvaluator::operator()(const GreaterThanEqualsFilter& filter) const {
optional<Value> actual = context.feature->getValue(filter.key);
return actual && compare(*actual, filter.value, [] (const auto& lhs_, const auto& rhs_) { return lhs_ >= rhs_; });
}
bool FilterEvaluator::operator()(const InFilter& filter) const {
optional<Value> actual = context.feature->getValue(filter.key);
if (!actual)
return false;
for (const auto& v: filter.values) {
if (equal(*actual, v)) {
return true;
}
}
return false;
}
bool FilterEvaluator::operator()(const NotInFilter& filter) const {
optional<Value> actual = context.feature->getValue(filter.key);
if (!actual)
return true;
for (const auto& v: filter.values) {
if (equal(*actual, v)) {
return false;
}
}
return true;
}
bool FilterEvaluator::operator()(const AnyFilter& filter) const {
for (const auto& f: filter.filters) {
if (Filter::visit(f, *this)) {
return true;
}
}
return false;
}
bool FilterEvaluator::operator()(const AllFilter& filter) const {
for (const auto& f: filter.filters) {
if (!Filter::visit(f, *this)) {
return false;
}
}
return true;
}
bool FilterEvaluator::operator()(const NoneFilter& filter) const {
for (const auto& f: filter.filters) {
if (Filter::visit(f, *this)) {
return false;
}
}
return true;
}
bool FilterEvaluator::operator()(const HasFilter& filter) const {
return bool(context.feature->getValue(filter.key));
}
bool FilterEvaluator::operator()(const NotHasFilter& filter) const {
return !context.feature->getValue(filter.key);
}
bool FilterEvaluator::operator()(const TypeEqualsFilter& filter) const {
return context.feature->getType() == filter.value;
}
bool FilterEvaluator::operator()(const TypeNotEqualsFilter& filter) const {
return context.feature->getType() != filter.value;
}
bool FilterEvaluator::operator()(const TypeInFilter& filter) const {
for (const auto& v: filter.values) {
if (context.feature->getType() == v) {
return true;
}
}
return false;
}
bool FilterEvaluator::operator()(const TypeNotInFilter& filter) const {
for (const auto& v: filter.values) {
if (context.feature->getType() == v) {
return false;
}
}
return true;
}
bool FilterEvaluator::operator()(const IdentifierEqualsFilter& filter) const {
return context.feature->getID() == filter.value;
}
bool FilterEvaluator::operator()(const IdentifierNotEqualsFilter& filter) const {
return context.feature->getID() != filter.value;
}
bool FilterEvaluator::operator()(const IdentifierInFilter& filter) const {
for (const auto& v: filter.values) {
if (context.feature->getID() == v) {
return true;
}
}
return false;
}
bool FilterEvaluator::operator()(const IdentifierNotInFilter& filter) const {
for (const auto& v: filter.values) {
if (context.feature->getID() == v) {
return false;
}
}
return true;
}
bool FilterEvaluator::operator()(const HasIdentifierFilter&) const {
return bool(context.feature->getID());
}
bool FilterEvaluator::operator()(const NotHasIdentifierFilter&) const {
return !context.feature->getID();
}
bool FilterEvaluator::operator()(const ExpressionFilter& filter) const {
const expression::EvaluationResult result = filter.expression->evaluate(context);
if (result) {
const optional<bool> typed = expression::fromExpressionValue<bool>(*result);
return typed ? *typed : false;
}
return false;
}
} // namespace style
} // namespace mbgl

View File

@@ -3,7 +3,6 @@
#include <mbgl/renderer/query.hpp>
#include <mbgl/renderer/tile_parameters.hpp>
#include <mbgl/actor/scheduler.hpp>
#include <mbgl/style/filter_evaluator.hpp>
#include <mbgl/util/string.hpp>
#include <mbgl/tile/tile_observer.hpp>
#include <mbgl/style/custom_tile_loader.hpp>

View File

@@ -2,7 +2,6 @@
#include <mbgl/tile/geojson_tile_data.hpp>
#include <mbgl/renderer/query.hpp>
#include <mbgl/renderer/tile_parameters.hpp>
#include <mbgl/style/filter_evaluator.hpp>
namespace mbgl {

View File

@@ -16,7 +16,6 @@
#include <mbgl/storage/file_source.hpp>
#include <mbgl/geometry/feature_index.hpp>
#include <mbgl/map/transform_state.hpp>
#include <mbgl/style/filter_evaluator.hpp>
#include <mbgl/util/logging.hpp>
#include <mbgl/actor/scheduler.hpp>

View File

@@ -5,7 +5,6 @@
#include <mbgl/renderer/bucket_parameters.hpp>
#include <mbgl/renderer/group_by_layout.hpp>
#include <mbgl/style/filter.hpp>
#include <mbgl/style/filter_evaluator.hpp>
#include <mbgl/style/layers/symbol_layer_impl.hpp>
#include <mbgl/renderer/layers/render_symbol_layer.hpp>
#include <mbgl/renderer/buckets/symbol_bucket.hpp>

View File

@@ -13,6 +13,7 @@
using namespace mbgl;
using namespace mbgl::style;
using namespace mbgl::style::expression;
namespace {
@@ -67,18 +68,19 @@ TEST(Query, QueryRenderedFeaturesFilterLayer) {
TEST(Query, QueryRenderedFeaturesFilter) {
QueryTest test;
ParsingContext context;
auto zz = test.map.pixelForLatLng({ 0, 0 });
const EqualsFilter eqFilter = { "key1", std::string("value1") };
const Filter eqFilter(createCompoundExpression("filter-==", createLiteral("key1"), createLiteral("value1"), context));
auto features1 = test.frontend.getRenderer()->queryRenderedFeatures(zz, {{}, { eqFilter }});
EXPECT_EQ(features1.size(), 1u);
const IdentifierNotEqualsFilter idNotEqFilter = { std::string("feature1") };
const Filter idNotEqFilter(createCompoundExpression("!", std::move(*createCompoundExpression("filter-id-==", createLiteral("feature1"), context)), context));
auto features2 = test.frontend.getRenderer()->queryRenderedFeatures(zz, {{{ "layer4" }}, { idNotEqFilter }});
EXPECT_EQ(features2.size(), 0u);
const GreaterThanFilter gtFilter = { "key2", 1.0 };
const Filter gtFilter(createCompoundExpression("filter->", createLiteral("key2"), createLiteral(1.0), context));
auto features3 = test.frontend.getRenderer()->queryRenderedFeatures(zz, {{ }, { gtFilter }});
EXPECT_EQ(features3.size(), 1u);
}
@@ -108,16 +110,17 @@ TEST(Query, QuerySourceFeaturesOptionValidation) {
TEST(Query, QuerySourceFeaturesFilter) {
QueryTest test;
ParsingContext context;
const EqualsFilter eqFilter = { "key1", std::string("value1") };
const Filter eqFilter(createCompoundExpression("filter-==", createLiteral("key1"), createLiteral("value1"), context));
auto features1 = test.frontend.getRenderer()->querySourceFeatures("source4", {{}, { eqFilter }});
EXPECT_EQ(features1.size(), 1u);
const IdentifierNotEqualsFilter idNotEqFilter = { std::string("feature1") };
const Filter idNotEqFilter(createCompoundExpression("!", std::move(*createCompoundExpression("filter-id-==", createLiteral("feature1"), context)), context));
auto features2 = test.frontend.getRenderer()->querySourceFeatures("source4", {{}, { idNotEqFilter }});
EXPECT_EQ(features2.size(), 0u);
const GreaterThanFilter gtFilter = { "key2", 1.0 };
const Filter gtFilter(createCompoundExpression("filter->", createLiteral("key2"), createLiteral(1.0), context));
auto features3 = test.frontend.getRenderer()->querySourceFeatures("source4", {{}, { gtFilter }});
EXPECT_EQ(features3.size(), 1u);
}

View File

@@ -5,9 +5,11 @@
#include <mbgl/style/layers/background_layer.hpp>
#include <mbgl/style/layers/circle_layer.hpp>
#include <mbgl/style/layers/line_layer.hpp>
#include <mbgl/style/expression/compound_expression.hpp>
using namespace mbgl;
using namespace mbgl::style;
using namespace mbgl::style::expression;
static std::vector<std::unique_ptr<RenderLayer>> toRenderLayers(const std::vector<std::unique_ptr<Layer>>& layers) {
std::vector<std::unique_ptr<RenderLayer>> result;
@@ -39,7 +41,8 @@ TEST(GroupByLayout, UnrelatedFilter) {
std::vector<std::unique_ptr<Layer>> layers;
layers.push_back(std::make_unique<LineLayer>("a", "source"));
layers.push_back(std::make_unique<LineLayer>("b", "source"));
layers[0]->as<LineLayer>()->setFilter(EqualsFilter());
ParsingContext context;
layers[0]->as<LineLayer>()->setFilter(Filter(createCompoundExpression("filter-has-id", context)));
auto result = groupByLayout(toRenderLayers(layers));
ASSERT_EQ(2u, result.size());
}

View File

@@ -1,5 +1,6 @@
#include <mbgl/test/util.hpp>
#include <mbgl/style/expression/literal.hpp>
#include <mbgl/style/conversion/stringify.hpp>
#include <mbgl/style/types.hpp>
#include <mbgl/style/layers/symbol_layer_properties.hpp>
@@ -75,8 +76,12 @@ TEST(Stringify, Value) {
}
TEST(Stringify, Filter) {
ASSERT_EQ(stringify(NullFilter()), "null");
ASSERT_EQ(stringify(EqualsFilter { "a", 1.0 }), "[\"==\",\"a\",1.0]");
using namespace mbgl::style::expression;
ASSERT_EQ(stringify(Filter()), "null");
ParsingContext context;
ASSERT_EQ(stringify(Filter(createCompoundExpression("filter-==", createLiteral("a"), createLiteral(1.0), context))), "[\"filter-==\",\"a\",1.0]");
}
TEST(Stringify, CameraFunction) {

View File

@@ -4,7 +4,6 @@
#include <mbgl/test/stub_geometry_tile_feature.hpp>
#include <mbgl/style/filter.hpp>
#include <mbgl/style/filter_evaluator.hpp>
#include <mbgl/style/conversion/json.hpp>
#include <mbgl/style/conversion/filter.hpp>
@@ -28,6 +27,24 @@ bool filter(const char * json,
return (*filter)(context);
}
void invalidFilter(const char * json) {
conversion::Error error;
optional<Filter> filter = conversion::convertJSON<Filter>(json, error);
EXPECT_FALSE(bool(filter));
EXPECT_NE(error.message, "");
}
TEST(Filter, EqualsNull) {
auto f = R"(["==", "foo", null])";
ASSERT_TRUE(filter(f, {{ "foo", mapbox::geometry::null_value }}));
ASSERT_FALSE(filter(f, {{ "foo", int64_t(0) }}));
ASSERT_FALSE(filter(f, {{ "foo", int64_t(1) }}));
ASSERT_FALSE(filter(f, {{ "foo", std::string("0") }}));
ASSERT_FALSE(filter(f, {{ "foo", true }}));
ASSERT_FALSE(filter(f, {{ "foo", false }}));
ASSERT_FALSE(filter(f, {{ }}));
}
TEST(Filter, EqualsString) {
auto f = R"(["==", "foo", "bar"])";
ASSERT_TRUE(filter(f, {{ "foo", std::string("bar") }}));
@@ -53,6 +70,12 @@ TEST(Filter, EqualsType) {
auto f = R"(["==", "$type", "LineString"])";
ASSERT_FALSE(filter(f, {{}}, {}, FeatureType::Point, {}));
ASSERT_TRUE(filter(f, {{}}, {}, FeatureType::LineString, {}));
ASSERT_FALSE(filter(f, {{}}, {}, FeatureType::Point, {}));
invalidFilter("[\"==\", \"$type\"]");
invalidFilter("[\"==\", \"$type\", null]");
invalidFilter("[\"==\", \"$type\", \"foo\", 1]");
invalidFilter("[\"==\", \"$type\", \"foo\", \"Point\"]");
}
TEST(Filter, InType) {
@@ -62,6 +85,14 @@ TEST(Filter, InType) {
ASSERT_TRUE(filter(f, {{}}, {}, FeatureType::Polygon));
}
TEST(Filter, InID) {
auto f = R"(["in", "$id", "123", "1234", 1234])";
ASSERT_FALSE(filter(f));
ASSERT_TRUE(filter(f, {{}}, { uint64_t(1234) }));
ASSERT_TRUE(filter(f, {{}}, { std::string("1234") }));
ASSERT_FALSE(filter(f, {{}}, { std::string("4321") }));
}
TEST(Filter, Any) {
ASSERT_FALSE(filter("[\"any\"]"));
ASSERT_TRUE(filter("[\"any\", [\"==\", \"foo\", 1]]", {{ std::string("foo"), int64_t(1) }}));
@@ -69,6 +100,13 @@ TEST(Filter, Any) {
ASSERT_TRUE(filter("[\"any\", [\"==\", \"foo\", 0], [\"==\", \"foo\", 1]]", {{ std::string("foo"), int64_t(1) }}));
}
TEST(Filter, AnyExpression) {
ASSERT_FALSE(filter("[\"any\"]"));
ASSERT_TRUE(filter("[\"any\", [\"==\", [\"get\", \"foo\"], 1]]", {{ std::string("foo"), int64_t(1) }}));
ASSERT_FALSE(filter("[\"any\", [\"==\", [\"get\", \"foo\"], 0]]", {{ std::string("foo"), int64_t(1) }}));
ASSERT_TRUE(filter("[\"any\", [\"==\", [\"get\", \"foo\"], 0], [\"==\", [\"get\", \"foo\"], 1]]", {{ std::string("foo"), int64_t(1) }}));
}
TEST(Filter, All) {
ASSERT_TRUE(filter("[\"all\"]", {{}}));
ASSERT_TRUE(filter("[\"all\", [\"==\", \"foo\", 1]]", {{ std::string("foo"), int64_t(1) }}));
@@ -76,6 +114,12 @@ TEST(Filter, All) {
ASSERT_FALSE(filter("[\"all\", [\"==\", \"foo\", 0], [\"==\", \"foo\", 1]]", {{ std::string("foo"), int64_t(1) }}));
}
TEST(Filter, AllExpression) {
ASSERT_TRUE(filter("[\"all\", [\"==\", [\"get\", \"foo\"], 1]]", {{ std::string("foo"), int64_t(1) }}));
ASSERT_FALSE(filter("[\"all\", [\"==\", [\"get\", \"foo\"], 0]]", {{ std::string("foo"), int64_t(1) }}));
ASSERT_FALSE(filter("[\"all\", [\"==\", [\"get\", \"foo\"], 0], [\"==\", [\"get\", \"foo\"], 1]]", {{ std::string("foo"), int64_t(1) }}));
}
TEST(Filter, None) {
ASSERT_TRUE(filter("[\"none\"]"));
ASSERT_FALSE(filter("[\"none\", [\"==\", \"foo\", 1]]", {{ std::string("foo"), int64_t(1) }}));
@@ -88,6 +132,7 @@ TEST(Filter, Has) {
ASSERT_TRUE(filter("[\"has\", \"foo\"]", {{ std::string("foo"), int64_t(0) }}));
ASSERT_TRUE(filter("[\"has\", \"foo\"]", {{ std::string("foo"), false }}));
ASSERT_FALSE(filter("[\"has\", \"foo\"]"));
ASSERT_FALSE(filter("[\"has\", \"$id\"]"));
}
TEST(Filter, NotHas) {
@@ -101,7 +146,11 @@ TEST(Filter, ID) {
FeatureIdentifier id1 { uint64_t{ 1234 } };
ASSERT_TRUE(filter("[\"==\", \"$id\", 1234]", {{}}, id1));
ASSERT_FALSE(filter("[\"==\", \"$id\", \"1234\"]", {{}}, id1));
FeatureIdentifier id2 { std::string{ "1" } };
ASSERT_FALSE(filter("[\"<\", \"$id\", \"0\"]", {{}}, id2));
ASSERT_TRUE(filter("[\"<\", \"$id\", \"1234\"]", {{}}, id2));
ASSERT_FALSE(filter("[\"==\", \"$id\", 1234]", {{ "id", uint64_t(1234) }}));
}
@@ -115,6 +164,18 @@ TEST(Filter, PropertyExpression) {
ASSERT_FALSE(filter("[\"==\", [\"get\", \"two\"], 4]", {{"two", int64_t(2)}}));
}
TEST(Filter, LegacyProperty) {
ASSERT_TRUE(filter("[\"<=\", \"two\", 2]", {{"two", int64_t(2)}}));
ASSERT_FALSE(filter("[\"==\", \"two\", 4]", {{"two", int64_t(2)}}));
ASSERT_FALSE(filter("[\"<=\", \"two\", \"2\"]", {{"two", int64_t(2)}}));
ASSERT_FALSE(filter("[\"==\", \"bool\", false]", {{"two", true}}));
ASSERT_TRUE(filter("[\"<=\", \"two\", \"2\"]", {{"two", std::string("2")}}));
ASSERT_FALSE(filter("[\"<\", \"two\", \"1\"]", {{"two", std::string("2")}}));
ASSERT_FALSE(filter("[\"==\", \"two\", 4]", {{"two", std::string("2")}}));
}
TEST(Filter, ZoomExpressionNested) {
ASSERT_TRUE(filter(R"(["==", ["get", "two"], ["zoom"]])", {{"two", int64_t(2)}}, {}, FeatureType::Point, {}, 2.0f));
ASSERT_FALSE(filter(R"(["==", ["get", "two"], ["+", ["zoom"], 1]])", {{"two", int64_t(2)}}, {}, FeatureType::Point, {}, 2.0f));

View File

@@ -211,7 +211,7 @@ TEST(Layer, Observer) {
EXPECT_EQ(layer.get(), &layer_);
filterChanged = true;
};
layer->setFilter(NullFilter());
layer->setFilter(Filter());
EXPECT_TRUE(filterChanged);
// Notifies observer on visibility change.