-
Notifications
You must be signed in to change notification settings - Fork 1.3k
[core] Add expression filter support #11251
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 looking good to me!
include/mbgl/style/filter.hpp
Outdated
std::shared_ptr<expression::Expression> expression; | ||
|
||
friend bool operator==(const ExpressionFilter& lhs, const ExpressionFilter& rhs) { | ||
return lhs.expression == rhs.expression; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that this is checking the pointers rather than the expression objects themselves. (*(lhs.expression) == *(rhs.expression)
to do a deep equality check)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you sure? I can't find a definitive answer but Googling around seems to indicate C++ will use ExpressionFilter::operator==
rather than an address check.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pretty sure -- according to http://en.cppreference.com/w/cpp/memory/shared_ptr/operator_cmp, shared_ptr::operator==
simply compares the wrapped Expression*
pointers, as opposed to invoking Expression::operator==()
. Confirmed here: http://cpp.sh/7yskf
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, doh, I was thinking about the ExpressionFilter&
s, not the shared_ptr<Expression>
s 😄
include/mbgl/style/filter.hpp
Outdated
|
||
class ExpressionFilter { | ||
public: | ||
std::shared_ptr<expression::Expression> expression; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could this be a unique_ptr
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think so. Will try that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Update: this won't work because ExpressionFilter
needs to be copyable
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can it be std::shared_ptr<const expression::Expression>
?
@@ -225,6 +225,10 @@ class StringifyFilter { | |||
void operator()(const NotHasIdentifierFilter&) { | |||
stringifyUnaryFilter("!has", "$id"); | |||
} | |||
|
|||
void operator()(const ExpressionFilter&) { | |||
stringifyUnaryFilter("herp", "derp"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#11156 should make this fairly straightforward
include/mbgl/style/filter.hpp
Outdated
|
||
template <class PropertyAccessor> | ||
bool operator()(FeatureType type, optional<FeatureIdentifier> id, PropertyAccessor accessor) const; | ||
bool operator()(expression::EvaluationContext context) const; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we remove the bool operator()(const GeometryTileFeature&) const
overload?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Absolutely 👍
@@ -194,6 +194,10 @@ | |||
NSPredicate *operator()(mbgl::style::NotHasIdentifierFilter filter) { | |||
return [NSPredicate predicateWithFormat:@"%K == nil", @"$id"]; | |||
} | |||
|
|||
NSPredicate *operator()(mbgl::style::ExpressionFilter filter) { | |||
return nil; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Once #11254 lands, the implementation should look something like this:
id jsonObject = MGLJSONObjectFromMBGLExpression(filter.expression);
return [NSPredicate mgl_predicateWithJSONObject:jsonObject];
52e3666
to
f1764dd
Compare
src/mbgl/geometry/feature_index.cpp
Outdated
@@ -125,7 +125,7 @@ void FeatureIndex::addFeature( | |||
continue; | |||
} | |||
|
|||
if (options.filter && !(*options.filter)(*geometryTileFeature)) { | |||
if (options.filter && !(*options.filter)(style::expression::EvaluationContext { geometryTileFeature.get() })) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- needs zoom
src/mbgl/layout/symbol_layout.cpp
Outdated
@@ -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 { feature.get() })) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- needs zoom
@@ -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 { feature.get() })) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- needs zoom
src/mbgl/tile/geojson_tile.cpp
Outdated
@@ -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 { feature.get() })) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- needs zoom
src/mbgl/tile/geometry_tile.cpp
Outdated
@@ -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 { feature.get() })) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- needs zoom
@@ -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 { feature.get() })) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- needs zoom
4c59cdb
to
bf13252
Compare
Background, raster, and custom layers do not have a filter, so we've avoided adding it to the base class. The appropriate concrete types already do have a |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks good, nice work! Just minor points.
include/mbgl/style/filter.hpp
Outdated
|
||
class ExpressionFilter { | ||
public: | ||
std::shared_ptr<expression::Expression> expression; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can it be std::shared_ptr<const expression::Expression>
?
include/mbgl/style/filter.hpp
Outdated
|
||
template <class PropertyAccessor> | ||
bool operator()(FeatureType type, optional<FeatureIdentifier> id, PropertyAccessor accessor) const; | ||
bool operator()(expression::EvaluationContext context) const; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const expression::EvaluationContext&
to avoid a copy.
src/mbgl/style/conversion/filter.cpp
Outdated
|
||
optional<std::string> op = toString(arrayMember(filter, 0)); | ||
|
||
if (*op == "has") { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Needs a guard conditional for if the first member is not a string (the GL JS implementation handles this implicitly in the default case).
src/mbgl/style/conversion/filter.cpp
Outdated
|
||
if (*op == "has") { | ||
auto operand = toString(arrayMember(filter, 1)); | ||
return arrayLength(filter) >= 2 && operand && *operand != "$id" && *operand != "$type"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The arrayLength
check needs to come before arrayMember
. Also needs a guard check for string type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
optional<std::string> operand = toString(arrayMember(filter, 1));
return operand && *operand != "$id" && *operand != "$type";
My understanding is that checking the truthiness of operand
serves as a guard check for string type. Is that the case?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, you're right, I missed that condition.
@jfirebaugh @anandthakker Is this ready to 🚢 🍏 pending a final sanity test? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM 🚀 🐑 !
include/mbgl/style/filter.hpp
Outdated
@@ -274,7 +274,7 @@ using FilterBase = variant< | |||
class Filter : public FilterBase { | |||
public: | |||
using FilterBase::FilterBase; | |||
bool operator()(expression::EvaluationContext context) const; | |||
bool operator()(const expression::EvaluationContext &context) const; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: const expression::EvaluationContext& context
(spacing around &
is not always consistent due to history, but this is our preferred style).
…lter 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
45b0db2
to
594bfd1
Compare
I missed that this was targeted at |
* 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
* 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
Resolves #10720
GL JS equivalent mapbox/mapbox-gl-js#5193
Todo
ExpressionFilter
type to include/mbgl/style/filter.hpp that holds anmbgl::style::Expression
object (of typembgl::style::expression::type::Boolean
)mbgl::style::FilterEvaluator
StringifyFilter::operator()(const ExpressionFilter&)
implementation using Implement Expression.serialize() (issue #10174) #11156Converter<Filter>::operator()
whether to parse the incoming JSON as anExpressionFilter
or one of the legacy filter types.bool Filter::operator()(const GeometryTileFeature&) const
filter.test.cpp
NSPredicate *operator()(mbgl::style::ExpressionFilter filter)
cc @1ec5 depends on Streamline mbgl expression to NSExpression conversion #11254 / Cherry-pick Expression.serialize() #11312Todo in Subsequent PRs
filter-*
expressions from GL JS, adding them to src/mbgl/style/expression/compound_expression.cpp.ExpressionFilter
(using thefilter-*
types as needed for legacy syntax)mbgl::style::FilterEvaluator
, removing all the different*Filter
types and theFilter
variant and renamingExpressionFilter
toFilter
.