Skip to content
This repository has been archived by the owner on Aug 8, 2023. It is now read-only.

[core] Add expression filter support #11251

Merged
merged 24 commits into from
Mar 8, 2018
Merged

[core] Add expression filter support #11251

merged 24 commits into from
Mar 8, 2018

Conversation

lucaswoj
Copy link
Contributor

@lucaswoj lucaswoj commented Feb 20, 2018

Resolves #10720

GL JS equivalent mapbox/mapbox-gl-js#5193

Todo

Todo in Subsequent PRs

@lucaswoj lucaswoj added Core The cross-platform C++ core, aka mbgl in progress labels Feb 20, 2018
Copy link
Contributor

@anandthakker anandthakker left a 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!

std::shared_ptr<expression::Expression> expression;

friend bool operator==(const ExpressionFilter& lhs, const ExpressionFilter& rhs) {
return lhs.expression == rhs.expression;
Copy link
Contributor

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)

Copy link
Contributor Author

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.

Copy link
Contributor

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

Copy link
Contributor Author

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 😄


class ExpressionFilter {
public:
std::shared_ptr<expression::Expression> expression;
Copy link
Contributor

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?

Copy link
Contributor Author

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.

Copy link
Contributor Author

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

Copy link
Contributor

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");
Copy link
Contributor

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


template <class PropertyAccessor>
bool operator()(FeatureType type, optional<FeatureIdentifier> id, PropertyAccessor accessor) const;
bool operator()(expression::EvaluationContext context) const;
Copy link
Contributor

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?

Copy link
Contributor Author

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;
Copy link
Contributor

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];

@@ -125,7 +125,7 @@ void FeatureIndex::addFeature(
continue;
}

if (options.filter && !(*options.filter)(*geometryTileFeature)) {
if (options.filter && !(*options.filter)(style::expression::EvaluationContext { geometryTileFeature.get() })) {
Copy link
Contributor Author

@lucaswoj lucaswoj Feb 26, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • needs zoom

@@ -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() }))
Copy link
Contributor Author

@lucaswoj lucaswoj Feb 26, 2018

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() })) {
Copy link
Contributor Author

@lucaswoj lucaswoj Feb 26, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • needs zoom

@@ -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() })) {
Copy link
Contributor Author

@lucaswoj lucaswoj Feb 26, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • needs zoom

@@ -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() })) {
Copy link
Contributor Author

@lucaswoj lucaswoj Feb 26, 2018

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() }))
Copy link
Contributor Author

@lucaswoj lucaswoj Feb 26, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • needs zoom

@lucaswoj lucaswoj changed the title Add expression filter support [core] Add expression filter support Feb 28, 2018
@jfirebaugh
Copy link
Contributor

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 getFilter accessor.

Copy link
Contributor

@jfirebaugh jfirebaugh left a 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.


class ExpressionFilter {
public:
std::shared_ptr<expression::Expression> expression;
Copy link
Contributor

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>?


template <class PropertyAccessor>
bool operator()(FeatureType type, optional<FeatureIdentifier> id, PropertyAccessor accessor) const;
bool operator()(expression::EvaluationContext context) const;
Copy link
Contributor

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.


optional<std::string> op = toString(arrayMember(filter, 0));

if (*op == "has") {
Copy link
Contributor

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).


if (*op == "has") {
auto operand = toString(arrayMember(filter, 1));
return arrayLength(filter) >= 2 && operand && *operand != "$id" && *operand != "$type";
Copy link
Contributor

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.

Copy link
Contributor Author

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?

Copy link
Contributor

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.

@lucaswoj
Copy link
Contributor Author

lucaswoj commented Mar 6, 2018

@jfirebaugh @anandthakker Is this ready to 🚢 🍏 pending a final sanity test?

Copy link
Contributor

@anandthakker anandthakker left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 🚀 🐑 !

@@ -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;
Copy link
Contributor

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).

@jfirebaugh
Copy link
Contributor

I missed that this was targeted at master. Can you please open a PR cherry-picking it to boba-release?

tobrun pushed a commit that referenced this pull request Mar 9, 2018
* 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
tobrun pushed a commit that referenced this pull request Mar 9, 2018
* 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
@tobrun tobrun mentioned this pull request Mar 9, 2018
9 tasks
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Core The cross-platform C++ core, aka mbgl
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants