diff options
author | Lucas Wojciechowski <lucas@lucaswoj.com> | 2018-03-08 15:48:08 -0800 |
---|---|---|
committer | Tobrun <tobrun@mapbox.com> | 2018-03-09 11:12:14 +0100 |
commit | 8a3deab73373c8f3182e4337a3619ebcc58ecc2c (patch) | |
tree | 46774560da0eac1e710dccbf41af2f2e7e8794c4 | |
parent | 6179110b6bfe8ee0880a055a31c12a88f9bd9b20 (diff) | |
download | qtlocation-mapboxgl-8a3deab73373c8f3182e4337a3619ebcc58ecc2c.tar.gz |
[core] Add expression filter support (#11251)
* WIP
* WIP
* WIP
* Remove Filter::operator()(const Feature&)
* WIP
* WIP
* WIP
* WIP
* Hook up expression filter evaluator
* Replace `shared_ptr` with &reference
* Fill in implementation of `void operator()(const ExpressionFilter&)`
* Fix failing tests
* Switch back to a shared_ptr per chat with @anandthakker
* Fix benchmark compilation
* Shot in the dark to fix CI
* Shot in the dark to fix CI (part 2)
* Shot in the dark to fix CI (part 3)
* In src/mbgl/style/conversion/filter.cpp, add a port of isExpressionFilter and use it to decide in Converter<Filter>::operator() whether to parse the incoming JSON as an ExpressionFilter or one of the legacy filter types
* Remove bool Filter::operator()(const GeometryTileFeature&) const
* Ensure the map zoom is passed into filtering operations wherever applicable
* Add expression filter tests
* Addressed PR feedback
* Implement `NSPredicate *operator()(mbgl::style::ExpressionFilter filter)`
* Fix formatting& nit
-rw-r--r-- | benchmark/fixtures/api/cache.db | bin | 1298432 -> 1298432 bytes | |||
-rw-r--r-- | benchmark/parse/filter.benchmark.cpp | 11 | ||||
-rw-r--r-- | cmake/core-files.cmake | 2 | ||||
-rw-r--r-- | include/mbgl/style/filter.hpp | 22 | ||||
-rw-r--r-- | include/mbgl/style/filter_evaluator.hpp | 259 | ||||
-rw-r--r-- | platform/darwin/src/NSPredicate+MGLAdditions.mm | 6 | ||||
-rw-r--r-- | src/mbgl/geometry/feature_index.cpp | 2 | ||||
-rw-r--r-- | src/mbgl/layout/symbol_layout.cpp | 2 | ||||
-rw-r--r-- | src/mbgl/style/conversion/filter.cpp | 65 | ||||
-rw-r--r-- | src/mbgl/style/conversion/stringify.hpp | 4 | ||||
-rw-r--r-- | src/mbgl/style/filter.cpp | 13 | ||||
-rw-r--r-- | src/mbgl/style/filter_evaluator.cpp | 225 | ||||
-rw-r--r-- | src/mbgl/tile/custom_geometry_tile.cpp | 2 | ||||
-rw-r--r-- | src/mbgl/tile/geojson_tile.cpp | 2 | ||||
-rw-r--r-- | src/mbgl/tile/geometry_tile.cpp | 2 | ||||
-rw-r--r-- | src/mbgl/tile/geometry_tile_worker.cpp | 2 | ||||
-rw-r--r-- | test/style/filter.test.cpp | 140 |
17 files changed, 423 insertions, 336 deletions
diff --git a/benchmark/fixtures/api/cache.db b/benchmark/fixtures/api/cache.db Binary files differindex 6a1d60421f..10452b2714 100644 --- a/benchmark/fixtures/api/cache.db +++ b/benchmark/fixtures/api/cache.db diff --git a/benchmark/parse/filter.benchmark.cpp b/benchmark/parse/filter.benchmark.cpp index 4984668400..e4cf635256 100644 --- a/benchmark/parse/filter.benchmark.cpp +++ b/benchmark/parse/filter.benchmark.cpp @@ -6,6 +6,7 @@ #include <mbgl/style/conversion/json.hpp> #include <mbgl/style/conversion/filter.hpp> #include <mbgl/tile/geometry_tile_data.hpp> +#include <mbgl/benchmark/stub_geometry_tile_feature.hpp> using namespace mbgl; @@ -22,15 +23,11 @@ static void Parse_Filter(benchmark::State& state) { static void Parse_EvaluateFilter(benchmark::State& state) { const style::Filter filter = parse(R"FILTER(["==", "foo", "bar"])FILTER"); - const PropertyMap properties = { { "foo", std::string("bar") } }; + const StubGeometryTileFeature feature = { {}, FeatureType::Unknown , {}, {{ "foo", std::string("bar") }} }; + const style::expression::EvaluationContext context = { &feature }; while (state.KeepRunning()) { - filter(FeatureType::Unknown, {}, [&] (const std::string& key) -> optional<Value> { - auto it = properties.find(key); - if (it == properties.end()) - return {}; - return it->second; - }); + filter(context); } } diff --git a/cmake/core-files.cmake b/cmake/core-files.cmake index a915ffed69..dd39395e5a 100644 --- a/cmake/core-files.cmake +++ b/cmake/core-files.cmake @@ -376,6 +376,8 @@ set(MBGL_CORE_FILES src/mbgl/style/collection.hpp 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 diff --git a/include/mbgl/style/filter.hpp b/include/mbgl/style/filter.hpp index a204a2b17a..ccf8dce188 100644 --- a/include/mbgl/style/filter.hpp +++ b/include/mbgl/style/filter.hpp @@ -3,6 +3,7 @@ #include <mbgl/util/variant.hpp> #include <mbgl/util/feature.hpp> #include <mbgl/util/geometry.hpp> +#include <mbgl/style/expression/expression.hpp> #include <string> #include <vector> @@ -232,6 +233,15 @@ public: return true; } }; + +class ExpressionFilter { +public: + std::shared_ptr<const expression::Expression> expression; + + friend bool operator==(const ExpressionFilter& lhs, const ExpressionFilter& rhs) { + return *(lhs.expression) == *(rhs.expression); + } +}; using FilterBase = variant< @@ -258,19 +268,13 @@ using FilterBase = variant< class IdentifierInFilter, class IdentifierNotInFilter, class HasIdentifierFilter, - class NotHasIdentifierFilter>; + class NotHasIdentifierFilter, + class ExpressionFilter>; class Filter : public FilterBase { public: using FilterBase::FilterBase; - - bool operator()(const Feature&) const; - - template <class GeometryTileFeature> - bool operator()(const GeometryTileFeature&) const; - - template <class PropertyAccessor> - bool operator()(FeatureType type, optional<FeatureIdentifier> id, PropertyAccessor accessor) const; + bool operator()(const expression::EvaluationContext& context) const; }; } // namespace style diff --git a/include/mbgl/style/filter_evaluator.hpp b/include/mbgl/style/filter_evaluator.hpp index 66223d7282..a4a4098b3f 100644 --- a/include/mbgl/style/filter_evaluator.hpp +++ b/include/mbgl/style/filter_evaluator.hpp @@ -19,242 +19,37 @@ namespace style { // does not match } */ -template <class PropertyAccessor> class FilterEvaluator { public: - const FeatureType featureType; - const optional<FeatureIdentifier> featureIdentifier; - const PropertyAccessor propertyAccessor; + 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; - bool operator()(const NullFilter&) const { - return true; - } - - bool operator()(const EqualsFilter& filter) const { - optional<Value> actual = propertyAccessor(filter.key); - return actual && equal(*actual, filter.value); - } - - bool operator()(const NotEqualsFilter& filter) const { - optional<Value> actual = propertyAccessor(filter.key); - return !actual || !equal(*actual, filter.value); - } - - bool operator()(const LessThanFilter& filter) const { - optional<Value> actual = propertyAccessor(filter.key); - return actual && compare(*actual, filter.value, [] (const auto& lhs_, const auto& rhs_) { return lhs_ < rhs_; }); - } - - bool operator()(const LessThanEqualsFilter& filter) const { - optional<Value> actual = propertyAccessor(filter.key); - return actual && compare(*actual, filter.value, [] (const auto& lhs_, const auto& rhs_) { return lhs_ <= rhs_; }); - } - - bool operator()(const GreaterThanFilter& filter) const { - optional<Value> actual = propertyAccessor(filter.key); - return actual && compare(*actual, filter.value, [] (const auto& lhs_, const auto& rhs_) { return lhs_ > rhs_; }); - } - - bool operator()(const GreaterThanEqualsFilter& filter) const { - optional<Value> actual = propertyAccessor(filter.key); - return actual && compare(*actual, filter.value, [] (const auto& lhs_, const auto& rhs_) { return lhs_ >= rhs_; }); - } - - bool operator()(const InFilter& filter) const { - optional<Value> actual = propertyAccessor(filter.key); - if (!actual) - return false; - for (const auto& v: filter.values) { - if (equal(*actual, v)) { - return true; - } - } - return false; - } - - bool operator()(const NotInFilter& filter) const { - optional<Value> actual = propertyAccessor(filter.key); - if (!actual) - return true; - for (const auto& v: filter.values) { - if (equal(*actual, v)) { - return false; - } - } - return true; - } - - bool operator()(const AnyFilter& filter) const { - for (const auto& f: filter.filters) { - if (Filter::visit(f, *this)) { - return true; - } - } - return false; - } - - bool operator()(const AllFilter& filter) const { - for (const auto& f: filter.filters) { - if (!Filter::visit(f, *this)) { - return false; - } - } - return true; - } - - bool operator()(const NoneFilter& filter) const { - for (const auto& f: filter.filters) { - if (Filter::visit(f, *this)) { - return false; - } - } - return true; - } - - bool operator()(const HasFilter& filter) const { - return bool(propertyAccessor(filter.key)); - } - - bool operator()(const NotHasFilter& filter) const { - return !propertyAccessor(filter.key); - } - - - bool operator()(const TypeEqualsFilter& filter) const { - return featureType == filter.value; - } - - bool operator()(const TypeNotEqualsFilter& filter) const { - return featureType != filter.value; - } - - bool operator()(const TypeInFilter& filter) const { - for (const auto& v: filter.values) { - if (featureType == v) { - return true; - } - } - return false; - } - - bool operator()(const TypeNotInFilter& filter) const { - for (const auto& v: filter.values) { - if (featureType == v) { - return false; - } - } - return true; - } - - - bool operator()(const IdentifierEqualsFilter& filter) const { - return featureIdentifier == filter.value; - } - - bool operator()(const IdentifierNotEqualsFilter& filter) const { - return featureIdentifier != filter.value; - } - - bool operator()(const IdentifierInFilter& filter) const { - for (const auto& v: filter.values) { - if (featureIdentifier == v) { - return true; - } - } - return false; - } - - bool operator()(const IdentifierNotInFilter& filter) const { - for (const auto& v: filter.values) { - if (featureIdentifier == v) { - return false; - } - } - return true; - } - - bool operator()(const HasIdentifierFilter&) const { - return bool(featureIdentifier); - } - - bool operator()(const NotHasIdentifierFilter&) const { - return !featureIdentifier; - } - -private: - 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) const { - return Value::binary_visit(lhs, rhs, Comparator<Op> { op }); - } - - bool equal(const Value& lhs, const Value& rhs) const { - return compare(lhs, rhs, [] (const auto& lhs_, const auto& rhs_) { return lhs_ == rhs_; }); - } }; -inline bool Filter::operator()(const Feature& feature) const { - return operator()(apply_visitor(ToFeatureType(), feature.geometry), feature.id, [&] (const std::string& key) -> optional<Value> { - auto it = feature.properties.find(key); - if (it == feature.properties.end()) - return {}; - return it->second; - }); -} - -template <class GeometryTileFeature> -bool Filter::operator()(const GeometryTileFeature& feature) const { - return operator()(feature.getType(), feature.getID(), [&] (const auto& key) { return feature.getValue(key); }); -} - -template <class PropertyAccessor> -bool Filter::operator()(FeatureType type, optional<FeatureIdentifier> id, PropertyAccessor accessor) const { - return FilterBase::visit(*this, FilterEvaluator<PropertyAccessor> { type, id, accessor }); -} - } // namespace style } // namespace mbgl diff --git a/platform/darwin/src/NSPredicate+MGLAdditions.mm b/platform/darwin/src/NSPredicate+MGLAdditions.mm index 3ffe200d01..63c8307803 100644 --- a/platform/darwin/src/NSPredicate+MGLAdditions.mm +++ b/platform/darwin/src/NSPredicate+MGLAdditions.mm @@ -1,6 +1,7 @@ #import "NSPredicate+MGLAdditions.h" #import "MGLValueEvaluator.h" +#import "MGLStyleValue_Private.h" class FilterEvaluator { public: @@ -194,6 +195,11 @@ public: 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 mgl_predicateWithJSONObject:jsonObject]; + } }; @implementation NSPredicate (MGLAdditions) diff --git a/src/mbgl/geometry/feature_index.cpp b/src/mbgl/geometry/feature_index.cpp index 3b5e12b54a..6fb0d5e446 100644 --- a/src/mbgl/geometry/feature_index.cpp +++ b/src/mbgl/geometry/feature_index.cpp @@ -125,7 +125,7 @@ void FeatureIndex::addFeature( continue; } - if (options.filter && !(*options.filter)(*geometryTileFeature)) { + if (options.filter && !(*options.filter)(style::expression::EvaluationContext { static_cast<float>(tileID.z), geometryTileFeature.get() })) { continue; } diff --git a/src/mbgl/layout/symbol_layout.cpp b/src/mbgl/layout/symbol_layout.cpp index a41a98fcaf..3bf85407c6 100644 --- a/src/mbgl/layout/symbol_layout.cpp +++ b/src/mbgl/layout/symbol_layout.cpp @@ -100,7 +100,7 @@ SymbolLayout::SymbolLayout(const BucketParameters& parameters, const size_t featureCount = sourceLayer->featureCount(); for (size_t i = 0; i < featureCount; ++i) { auto feature = sourceLayer->getFeature(i); - if (!leader.filter(feature->getType(), feature->getID(), [&] (const auto& key) { return feature->getValue(key); })) + if (!leader.filter(expression::EvaluationContext { this->zoom, feature.get() })) continue; SymbolFeature ft(std::move(feature)); diff --git a/src/mbgl/style/conversion/filter.cpp b/src/mbgl/style/conversion/filter.cpp index bb7bb6ea98..fba149da12 100644 --- a/src/mbgl/style/conversion/filter.cpp +++ b/src/mbgl/style/conversion/filter.cpp @@ -1,11 +1,52 @@ #include <mbgl/style/conversion/filter.hpp> #include <mbgl/util/geometry.hpp> +#include <mbgl/style/expression/expression.hpp> +#include <mbgl/style/expression/type.hpp> +#include <mbgl/style/conversion/expression.hpp> namespace mbgl { namespace style { namespace conversion { + +using GeometryValue = mapbox::geometry::value; -static optional<Value> normalizeValue(const optional<Value>& value, Error& error) { +// 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) { + if (!isArray(filter) || arrayLength(filter) == 0) { + return false; + } + + optional<std::string> op = toString(arrayMember(filter, 0)); + + if (!op) { + return false; + + } else if (*op == "has") { + if (arrayLength(filter) < 2) return false; + optional<std::string> operand = toString(arrayMember(filter, 1)); + return operand && *operand != "$id" && *operand != "$type"; + + } else if (*op == "in" || *op == "!in" || *op == "!has" || *op == "none") { + return false; + + } else if (*op == "==" || *op == "!=" || *op == ">" || *op == ">=" || *op == "<" || *op == "<=") { + return arrayLength(filter) == 3 && (isArray(arrayMember(filter, 1)) || isArray(arrayMember(filter, 2))); + + } 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)) { + return false; + } + } + return true; + + } else { + return true; + } +} + +static optional<GeometryValue> normalizeValue(const optional<GeometryValue>& value, Error& error) { if (!value) { error = { "filter expression value must be a boolean, number, or string" }; return {}; @@ -32,7 +73,7 @@ static optional<FeatureType> toFeatureType(const Convertible& value, Error& erro } static optional<FeatureIdentifier> toFeatureIdentifier(const Convertible& value, Error& error) { - optional<Value> identifier = toValue(value); + optional<GeometryValue> identifier = toValue(value); if (!identifier) { error = { "filter expression value must be a boolean, number, or string" }; return {}; @@ -99,7 +140,7 @@ optional<Filter> convertEqualityFilter(const Convertible& value, Error& error) { return { IdentifierFilterType { *filterValue } }; } else { - optional<Value> filterValue = normalizeValue(toValue(arrayMember(value, 2)), error); + optional<GeometryValue> filterValue = normalizeValue(toValue(arrayMember(value, 2)), error); if (!filterValue) { return {}; } @@ -121,7 +162,7 @@ optional<Filter> convertBinaryFilter(const Convertible& value, Error& error) { return {}; } - optional<Value> filterValue = normalizeValue(toValue(arrayMember(value, 2)), error); + optional<GeometryValue> filterValue = normalizeValue(toValue(arrayMember(value, 2)), error); if (!filterValue) { return {}; } @@ -167,9 +208,9 @@ optional<Filter> convertSetFilter(const Convertible& value, Error& error) { return { IdentifierFilterType { std::move(values) } }; } else { - std::vector<Value> values; + std::vector<GeometryValue> values; for (std::size_t i = 2; i < arrayLength(value); ++i) { - optional<Value> filterValue = normalizeValue(toValue(arrayMember(value, i)), error); + optional<GeometryValue> filterValue = normalizeValue(toValue(arrayMember(value, i)), error); if (!filterValue) { return {}; } @@ -193,8 +234,20 @@ optional<Filter> convertCompoundFilter(const Convertible& value, Error& error) { return { FilterType { std::move(filters) } }; } + +optional<Filter> convertExpressionFilter(const Convertible& value, Error& error) { + optional<std::unique_ptr<Expression>> expression = convert<std::unique_ptr<Expression>>(value, error, expression::type::Boolean); + if (!expression) { + return {}; + } + return { ExpressionFilter { std::move(*expression) } }; +} optional<Filter> Converter<Filter>::operator()(const Convertible& value, Error& error) const { + if (isExpressionFilter(value)) { + return convertExpressionFilter(value, error); + } + if (!isArray(value)) { error = { "filter expression must be an array" }; return {}; diff --git a/src/mbgl/style/conversion/stringify.hpp b/src/mbgl/style/conversion/stringify.hpp index 6ae6fede42..7924a442c4 100644 --- a/src/mbgl/style/conversion/stringify.hpp +++ b/src/mbgl/style/conversion/stringify.hpp @@ -225,6 +225,10 @@ public: void operator()(const NotHasIdentifierFilter&) { stringifyUnaryFilter("!has", "$id"); } + + void operator()(const ExpressionFilter& filter) { + stringify(writer, filter.expression->serialize()); + } private: template <class F> diff --git a/src/mbgl/style/filter.cpp b/src/mbgl/style/filter.cpp new file mode 100644 index 0000000000..51aa6bcf82 --- /dev/null +++ b/src/mbgl/style/filter.cpp @@ -0,0 +1,13 @@ +#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 }); +} + +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/filter_evaluator.cpp b/src/mbgl/style/filter_evaluator.cpp new file mode 100644 index 0000000000..72022172f4 --- /dev/null +++ b/src/mbgl/style/filter_evaluator.cpp @@ -0,0 +1,225 @@ +#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 diff --git a/src/mbgl/tile/custom_geometry_tile.cpp b/src/mbgl/tile/custom_geometry_tile.cpp index 33962ad87d..a2fefcfa9f 100644 --- a/src/mbgl/tile/custom_geometry_tile.cpp +++ b/src/mbgl/tile/custom_geometry_tile.cpp @@ -79,7 +79,7 @@ void CustomGeometryTile::querySourceFeatures( auto feature = layer->getFeature(i); // Apply filter, if any - if (queryOptions.filter && !(*queryOptions.filter)(*feature)) { + if (queryOptions.filter && !(*queryOptions.filter)(style::expression::EvaluationContext { static_cast<float>(id.overscaledZ), feature.get() })) { continue; } diff --git a/src/mbgl/tile/geojson_tile.cpp b/src/mbgl/tile/geojson_tile.cpp index bbec899950..f211c03569 100644 --- a/src/mbgl/tile/geojson_tile.cpp +++ b/src/mbgl/tile/geojson_tile.cpp @@ -30,7 +30,7 @@ void GeoJSONTile::querySourceFeatures( auto feature = layer->getFeature(i); // Apply filter, if any - if (options.filter && !(*options.filter)(*feature)) { + if (options.filter && !(*options.filter)(style::expression::EvaluationContext { static_cast<float>(this->id.overscaledZ), feature.get() })) { continue; } diff --git a/src/mbgl/tile/geometry_tile.cpp b/src/mbgl/tile/geometry_tile.cpp index a58c744065..82d0c91806 100644 --- a/src/mbgl/tile/geometry_tile.cpp +++ b/src/mbgl/tile/geometry_tile.cpp @@ -281,7 +281,7 @@ void GeometryTile::querySourceFeatures( auto feature = layer->getFeature(i); // Apply filter, if any - if (options.filter && !(*options.filter)(*feature)) { + if (options.filter && !(*options.filter)(style::expression::EvaluationContext { static_cast<float>(this->id.overscaledZ), feature.get() })) { continue; } diff --git a/src/mbgl/tile/geometry_tile_worker.cpp b/src/mbgl/tile/geometry_tile_worker.cpp index 24841dd125..8b6f1f63bf 100644 --- a/src/mbgl/tile/geometry_tile_worker.cpp +++ b/src/mbgl/tile/geometry_tile_worker.cpp @@ -339,7 +339,7 @@ void GeometryTileWorker::redoLayout() { for (std::size_t i = 0; !obsolete && i < geometryLayer->featureCount(); i++) { std::unique_ptr<GeometryTileFeature> feature = geometryLayer->getFeature(i); - if (!filter(feature->getType(), feature->getID(), [&] (const auto& key) { return feature->getValue(key); })) + if (!filter(expression::EvaluationContext { static_cast<float>(this->id.overscaledZ), feature.get() })) continue; GeometryCollection geometries = feature->getGeometries(); diff --git a/test/style/filter.test.cpp b/test/style/filter.test.cpp index 73f8e7626d..6f261c43ec 100644 --- a/test/style/filter.test.cpp +++ b/test/style/filter.test.cpp @@ -1,6 +1,7 @@ #include <mbgl/test/util.hpp> #include <mbgl/util/feature.hpp> #include <mbgl/util/geometry.hpp> +#include <mbgl/test/stub_geometry_tile_feature.hpp> #include <mbgl/style/filter.hpp> #include <mbgl/style/filter_evaluator.hpp> @@ -10,114 +11,101 @@ using namespace mbgl; using namespace mbgl::style; -Filter parse(const char * expression) { +bool filter(const char * json, const PropertyMap& featureProperties = {{}}, optional<FeatureIdentifier> featureId = {}, FeatureType featureType = FeatureType::Point, GeometryCollection featureGeometry = {}) { conversion::Error error; - optional<Filter> filter = conversion::convertJSON<Filter>(expression, error); + optional<Filter> filter = conversion::convertJSON<Filter>(json, error); EXPECT_TRUE(bool(filter)); - return *filter; -} - -Feature feature(const PropertyMap& properties, const Geometry<double>& geometry = Point<double>()) { - Feature result { geometry }; - result.properties = properties; - return result; + EXPECT_EQ(error.message, ""); + + StubGeometryTileFeature feature { featureId, featureType, featureGeometry, featureProperties }; + expression::EvaluationContext context = { &feature }; + + return (*filter)(context); } TEST(Filter, EqualsString) { - Filter f = parse(R"(["==", "foo", "bar"])"); - ASSERT_TRUE(f(feature({{ "foo", std::string("bar") }}))); - ASSERT_FALSE(f(feature({{ "foo", std::string("baz") }}))); + auto f = R"(["==", "foo", "bar"])"; + ASSERT_TRUE(filter(f, {{ "foo", std::string("bar") }})); + ASSERT_FALSE(filter(f, {{ "foo", std::string("baz") }})); } TEST(Filter, EqualsNumber) { - Filter f = parse(R"(["==", "foo", 0])"); - ASSERT_TRUE(f(feature({{ "foo", int64_t(0) }}))); - ASSERT_TRUE(f(feature({{ "foo", uint64_t(0) }}))); - ASSERT_TRUE(f(feature({{ "foo", double(0) }}))); - ASSERT_FALSE(f(feature({{ "foo", int64_t(1) }}))); - ASSERT_FALSE(f(feature({{ "foo", uint64_t(1) }}))); - ASSERT_FALSE(f(feature({{ "foo", double(1) }}))); - ASSERT_FALSE(f(feature({{ "foo", std::string("0") }}))); - ASSERT_FALSE(f(feature({{ "foo", false }}))); - ASSERT_FALSE(f(feature({{ "foo", true }}))); - ASSERT_FALSE(f(feature({{ "foo", mapbox::geometry::null_value }}))); - ASSERT_FALSE(f(feature({{}}))); + auto f = R"(["==", "foo", 0])"; + ASSERT_TRUE(filter(f, {{ "foo", int64_t(0) }})); + ASSERT_TRUE(filter(f, {{ "foo", uint64_t(0) }})); + ASSERT_TRUE(filter(f, {{ "foo", double(0) }})); + ASSERT_FALSE(filter(f, {{ "foo", int64_t(1) }})); + ASSERT_FALSE(filter(f, {{ "foo", uint64_t(1) }})); + ASSERT_FALSE(filter(f, {{ "foo", double(1) }})); + ASSERT_FALSE(filter(f, {{ "foo", std::string("0") }})); + ASSERT_FALSE(filter(f, {{ "foo", false }})); + ASSERT_FALSE(filter(f, {{ "foo", true }})); + ASSERT_FALSE(filter(f, {{ "foo", mapbox::geometry::null_value }})); + ASSERT_FALSE(filter(f, {{}})); } TEST(Filter, EqualsType) { - Filter f = parse(R"(["==", "$type", "LineString"])"); - ASSERT_FALSE(f(feature({{}}, Point<double>()))); - ASSERT_TRUE(f(feature({{}}, LineString<double>()))); + auto f = R"(["==", "$type", "LineString"])"; + ASSERT_FALSE(filter(f, {{}}, {}, FeatureType::Point, {})); + ASSERT_TRUE(filter(f, {{}}, {}, FeatureType::LineString, {})); } TEST(Filter, InType) { - Filter f = parse(R"(["in", "$type", "LineString", "Polygon"])"); - ASSERT_FALSE(f(feature({{}}, Point<double>()))); - ASSERT_TRUE(f(feature({{}}, LineString<double>()))); - ASSERT_TRUE(f(feature({{}}, Polygon<double>()))); + auto f = R"(["in", "$type", "LineString", "Polygon"])"; + ASSERT_FALSE(filter(f, {{}}, {}, FeatureType::Point)); + ASSERT_TRUE(filter(f, {{}}, {}, FeatureType::LineString)); + ASSERT_TRUE(filter(f, {{}}, {}, FeatureType::Polygon)); } TEST(Filter, Any) { - ASSERT_FALSE(parse("[\"any\"]")(feature({{}}))); - ASSERT_TRUE(parse("[\"any\", [\"==\", \"foo\", 1]]")( - feature({{ std::string("foo"), int64_t(1) }}))); - ASSERT_FALSE(parse("[\"any\", [\"==\", \"foo\", 0]]")( - feature({{ std::string("foo"), int64_t(1) }}))); - ASSERT_TRUE(parse("[\"any\", [\"==\", \"foo\", 0], [\"==\", \"foo\", 1]]")( - feature({{ std::string("foo"), int64_t(1) }}))); + ASSERT_FALSE(filter("[\"any\"]")); + ASSERT_TRUE(filter("[\"any\", [\"==\", \"foo\", 1]]", {{ std::string("foo"), int64_t(1) }})); + ASSERT_FALSE(filter("[\"any\", [\"==\", \"foo\", 0]]", {{ std::string("foo"), int64_t(1) }})); + ASSERT_TRUE(filter("[\"any\", [\"==\", \"foo\", 0], [\"==\", \"foo\", 1]]", {{ std::string("foo"), int64_t(1) }})); } TEST(Filter, All) { - ASSERT_TRUE(parse("[\"all\"]")(feature({{}}))); - ASSERT_TRUE(parse("[\"all\", [\"==\", \"foo\", 1]]")( - feature({{ std::string("foo"), int64_t(1) }}))); - ASSERT_FALSE(parse("[\"all\", [\"==\", \"foo\", 0]]")( - feature({{ std::string("foo"), int64_t(1) }}))); - ASSERT_FALSE(parse("[\"all\", [\"==\", \"foo\", 0], [\"==\", \"foo\", 1]]")( - feature({{ std::string("foo"), int64_t(1) }}))); + ASSERT_TRUE(filter("[\"all\"]", {{}})); + ASSERT_TRUE(filter("[\"all\", [\"==\", \"foo\", 1]]", {{ std::string("foo"), int64_t(1) }})); + ASSERT_FALSE(filter("[\"all\", [\"==\", \"foo\", 0]]", {{ std::string("foo"), int64_t(1) }})); + ASSERT_FALSE(filter("[\"all\", [\"==\", \"foo\", 0], [\"==\", \"foo\", 1]]", {{ std::string("foo"), int64_t(1) }})); } TEST(Filter, None) { - ASSERT_TRUE(parse("[\"none\"]")(feature({{}}))); - ASSERT_FALSE(parse("[\"none\", [\"==\", \"foo\", 1]]")( - feature({{ std::string("foo"), int64_t(1) }}))); - ASSERT_TRUE(parse("[\"none\", [\"==\", \"foo\", 0]]")( - feature({{ std::string("foo"), int64_t(1) }}))); - ASSERT_FALSE(parse("[\"none\", [\"==\", \"foo\", 0], [\"==\", \"foo\", 1]]")( - feature({{ std::string("foo"), int64_t(1) }}))); + ASSERT_TRUE(filter("[\"none\"]")); + ASSERT_FALSE(filter("[\"none\", [\"==\", \"foo\", 1]]", {{ std::string("foo"), int64_t(1) }})); + ASSERT_TRUE(filter("[\"none\", [\"==\", \"foo\", 0]]", {{ std::string("foo"), int64_t(1) }})); + ASSERT_FALSE(filter("[\"none\", [\"==\", \"foo\", 0], [\"==\", \"foo\", 1]]", {{ std::string("foo"), int64_t(1) }})); } TEST(Filter, Has) { - ASSERT_TRUE(parse("[\"has\", \"foo\"]")( - feature({{ std::string("foo"), int64_t(1) }}))); - ASSERT_TRUE(parse("[\"has\", \"foo\"]")( - feature({{ std::string("foo"), int64_t(0) }}))); - ASSERT_TRUE(parse("[\"has\", \"foo\"]")( - feature({{ std::string("foo"), false }}))); - ASSERT_FALSE(parse("[\"has\", \"foo\"]")( - feature({{}}))); + ASSERT_TRUE(filter("[\"has\", \"foo\"]", {{ std::string("foo"), int64_t(1) }})); + 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\"]")); } TEST(Filter, NotHas) { - ASSERT_FALSE(parse("[\"!has\", \"foo\"]")( - feature({{ std::string("foo"), int64_t(1) }}))); - ASSERT_FALSE(parse("[\"!has\", \"foo\"]")( - feature({{ std::string("foo"), int64_t(0) }}))); - ASSERT_FALSE(parse("[\"!has\", \"foo\"]")( - feature({{ std::string("foo"), false }}))); - ASSERT_TRUE(parse("[\"!has\", \"foo\"]")( - feature({{}}))); + ASSERT_FALSE(filter("[\"!has\", \"foo\"]", {{ std::string("foo"), int64_t(1) }})); + ASSERT_FALSE(filter("[\"!has\", \"foo\"]", {{ std::string("foo"), int64_t(0) }})); + ASSERT_FALSE(filter("[\"!has\", \"foo\"]", {{ std::string("foo"), false }})); + ASSERT_TRUE(filter("[\"!has\", \"foo\"]")); } TEST(Filter, ID) { - Feature feature1 { Point<double>() }; - feature1.id = { uint64_t(1234) }; - - ASSERT_TRUE(parse("[\"==\", \"$id\", 1234]")(feature1)); - ASSERT_FALSE(parse("[\"==\", \"$id\", \"1234\"]")(feature1)); + FeatureIdentifier id1 { uint64_t{ 1234 } }; + ASSERT_TRUE(filter("[\"==\", \"$id\", 1234]", {{}}, id1)); + ASSERT_FALSE(filter("[\"==\", \"$id\", \"1234\"]", {{}}, id1)); + + ASSERT_FALSE(filter("[\"==\", \"$id\", 1234]", {{ "id", uint64_t(1234) }})); +} - Feature feature2 { Point<double>() }; - feature2.properties["id"] = { uint64_t(1234) }; +TEST(Filter, Expression) { + ASSERT_TRUE(filter("[\"==\", [\"+\", 1, 1], 2]")); + ASSERT_FALSE(filter("[\"==\", [\"+\", 1, 1], 4]")); +} - ASSERT_FALSE(parse("[\"==\", \"$id\", 1234]")(feature2)); +TEST(Filter, PropertyExpression) { + ASSERT_TRUE(filter("[\"==\", [\"get\", \"two\"], 2]", {{"two", int64_t(2)}})); + ASSERT_FALSE(filter("[\"==\", [\"get\", \"two\"], 4]", {{"two", int64_t(2)}})); } |