From a1a639120d7662cb68f0ba5770e4f42cc9415069 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Thu, 9 Feb 2017 16:18:57 -0600 Subject: [core] Introduce dedicated filter types for $type and $id special cases (#7971) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [core] Introduce dedicated filter types for $type and $id special cases * [ios, macos] Special-case $id, $type in predicates Also support $id ≟ nil. --- include/mbgl/style/conversion/filter.hpp | 144 +++++++++++++++---- include/mbgl/style/filter.hpp | 101 +++++++++++++- include/mbgl/style/filter_evaluator.hpp | 92 +++++++++--- mapbox-gl-js | 2 +- .../src/NSComparisonPredicate+MGLAdditions.mm | 105 +++++++++++++- platform/darwin/src/NSExpression+MGLAdditions.h | 3 + platform/darwin/src/NSExpression+MGLAdditions.mm | 61 ++++++++ platform/darwin/src/NSPredicate+MGLAdditions.mm | 72 +++++++++- platform/darwin/test/MGLExpressionTests.mm | 19 +++ platform/darwin/test/MGLPredicateTests.mm | 155 +++++++++++++++++++++ platform/ios/CHANGELOG.md | 3 +- platform/macos/CHANGELOG.md | 3 +- src/mbgl/style/conversion/stringify.hpp | 86 +++++++++++- 13 files changed, 781 insertions(+), 65 deletions(-) diff --git a/include/mbgl/style/conversion/filter.hpp b/include/mbgl/style/conversion/filter.hpp index 3ab91e5bbc..2c4eeb4ac7 100644 --- a/include/mbgl/style/conversion/filter.hpp +++ b/include/mbgl/style/conversion/filter.hpp @@ -27,9 +27,9 @@ public: } if (*op == "==") { - return convertBinaryFilter(value); + return convertEqualityFilter(value); } else if (*op == "!=") { - return convertBinaryFilter(value); + return convertEqualityFilter(value); } else if (*op == ">") { return convertBinaryFilter(value); } else if (*op == ">=") { @@ -39,9 +39,9 @@ public: } else if (*op == "<=") { return convertBinaryFilter(value); } else if (*op == "in") { - return convertSetFilter(value); + return convertSetFilter(value); } else if (*op == "!in") { - return convertSetFilter(value); + return convertSetFilter(value); } else if (*op == "all") { return convertCompoundFilter(value); } else if (*op == "any") { @@ -49,32 +49,57 @@ public: } else if (*op == "none") { return convertCompoundFilter(value); } else if (*op == "has") { - return convertUnaryFilter(value); + return convertUnaryFilter(value); } else if (*op == "!has") { - return convertUnaryFilter(value); + return convertUnaryFilter(value); } return Error { "filter operator must be one of \"==\", \"!=\", \">\", \">=\", \"<\", \"<=\", \"in\", \"!in\", \"all\", \"any\", \"none\", \"has\", or \"!has\"" }; } private: - Result normalizeValue(const std::string& key, const optional& value) const { + Result normalizeValue(const optional& value) const { if (!value) { return Error { "filter expression value must be a boolean, number, or string" }; - } else if (key != "$type") { + } else { return *value; - } else if (*value == std::string("Point")) { - return Value(uint64_t(FeatureType::Point)); - } else if (*value == std::string("LineString")) { - return Value(uint64_t(FeatureType::LineString)); - } else if (*value == std::string("Polygon")) { - return Value(uint64_t(FeatureType::Polygon)); + } + } + + template + Result toFeatureType(const V& value) const { + optional type = toString(value); + if (!type) { + return Error { "value for $type filter must be a string" }; + } else if (*type == "Point") { + return FeatureType::Point; + } else if (*type == "LineString") { + return FeatureType::LineString; + } else if (*type == "Polygon") { + return FeatureType::Polygon; } else { return Error { "value for $type filter must be Point, LineString, or Polygon" }; } } - template + template + Result toFeatureIdentifier(const V& value) const { + optional identifier = toValue(value); + if (!identifier) { + return Error { "filter expression value must be a boolean, number, or string" }; + } else { + return (*identifier).match( + [] (uint64_t t) -> Result { return t; }, + [] ( int64_t t) -> Result { return t; }, + [] ( double t) -> Result { return t; }, + [] (const std::string& t) -> Result { return t; }, + [] (const auto&) -> Result { + return Error { "filter expression value must be a boolean, number, or string" }; + }); + } + } + + template Result convertUnaryFilter(const V& value) const { if (arrayLength(value) < 2) { return Error { "filter expression must have 2 elements" }; @@ -85,7 +110,48 @@ private: return Error { "filter expression key must be a string" }; } - return FilterType { *key }; + if (*key == "$id") { + return IdentifierFilterType {}; + } else { + return FilterType { *key }; + } + } + + template + Result convertEqualityFilter(const V& value) const { + if (arrayLength(value) < 3) { + return Error { "filter expression must have 3 elements" }; + } + + optional key = toString(arrayMember(value, 1)); + if (!key) { + return Error { "filter expression key must be a string" }; + } + + if (*key == "$type") { + Result filterValue = toFeatureType(arrayMember(value, 2)); + if (!filterValue) { + return filterValue.error(); + } + + return TypeFilterType { *filterValue }; + + } else if (*key == "$id") { + Result filterValue = toFeatureIdentifier(arrayMember(value, 2)); + if (!filterValue) { + return filterValue.error(); + } + + return IdentifierFilterType { *filterValue }; + + } else { + Result filterValue = normalizeValue(toValue(arrayMember(value, 2))); + if (!filterValue) { + return filterValue.error(); + } + + return FilterType { *key, *filterValue }; + } } template @@ -99,7 +165,7 @@ private: return Error { "filter expression key must be a string" }; } - Result filterValue = normalizeValue(*key, toValue(arrayMember(value, 2))); + Result filterValue = normalizeValue(toValue(arrayMember(value, 2))); if (!filterValue) { return filterValue.error(); } @@ -107,7 +173,7 @@ private: return FilterType { *key, *filterValue }; } - template + template Result convertSetFilter(const V& value) const { if (arrayLength(value) < 2) { return Error { "filter expression must at least 2 elements" }; @@ -118,16 +184,42 @@ private: return Error { "filter expression key must be a string" }; } - std::vector values; - for (std::size_t i = 2; i < arrayLength(value); ++i) { - Result filterValue = normalizeValue(*key, toValue(arrayMember(value, i))); - if (!filterValue) { - return filterValue.error(); + if (*key == "$type") { + std::vector values; + for (std::size_t i = 2; i < arrayLength(value); ++i) { + Result filterValue = toFeatureType(arrayMember(value, i)); + if (!filterValue) { + return filterValue.error(); + } + values.push_back(*filterValue); } - values.push_back(*filterValue); - } - return FilterType { *key, std::move(values) }; + return TypeFilterType { std::move(values) }; + + } else if (*key == "$id") { + std::vector values; + for (std::size_t i = 2; i < arrayLength(value); ++i) { + Result filterValue = toFeatureIdentifier(arrayMember(value, i)); + if (!filterValue) { + return filterValue.error(); + } + values.push_back(*filterValue); + } + + return IdentifierFilterType { std::move(values) }; + + } else { + std::vector values; + for (std::size_t i = 2; i < arrayLength(value); ++i) { + Result filterValue = normalizeValue(toValue(arrayMember(value, i))); + if (!filterValue) { + return filterValue.error(); + } + values.push_back(*filterValue); + } + + return FilterType { *key, std::move(values) }; + } } template diff --git a/include/mbgl/style/filter.hpp b/include/mbgl/style/filter.hpp index e5d8081d82..5e61adf064 100644 --- a/include/mbgl/style/filter.hpp +++ b/include/mbgl/style/filter.hpp @@ -145,6 +145,95 @@ public: } }; + +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 values; + + friend bool operator==(const TypeInFilter& lhs, const TypeInFilter& rhs) { + return lhs.values == rhs.values; + } +}; + +class TypeNotInFilter { +public: + std::vector 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 values; + + friend bool operator==(const IdentifierInFilter& lhs, const IdentifierInFilter& rhs) { + return lhs.values == rhs.values; + } +}; + +class IdentifierNotInFilter { +public: + std::vector 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; + } +}; + + using FilterBase = variant< class NullFilter, class EqualsFilter, @@ -159,7 +248,17 @@ using FilterBase = variant< class AllFilter, class NoneFilter, class HasFilter, - class NotHasFilter>; + class NotHasFilter, + class TypeEqualsFilter, + class TypeNotEqualsFilter, + class TypeInFilter, + class TypeNotInFilter, + class IdentifierEqualsFilter, + class IdentifierNotEqualsFilter, + class IdentifierInFilter, + class IdentifierNotInFilter, + class HasIdentifierFilter, + class NotHasIdentifierFilter>; class Filter : public FilterBase { public: diff --git a/include/mbgl/style/filter_evaluator.hpp b/include/mbgl/style/filter_evaluator.hpp index 659f554bba..370064445a 100644 --- a/include/mbgl/style/filter_evaluator.hpp +++ b/include/mbgl/style/filter_evaluator.hpp @@ -31,37 +31,37 @@ public: } bool operator()(const EqualsFilter& filter) const { - optional actual = getValue(filter.key); + optional actual = propertyAccessor(filter.key); return actual && equal(*actual, filter.value); } bool operator()(const NotEqualsFilter& filter) const { - optional actual = getValue(filter.key); + optional actual = propertyAccessor(filter.key); return !actual || !equal(*actual, filter.value); } bool operator()(const LessThanFilter& filter) const { - optional actual = getValue(filter.key); + optional 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 actual = getValue(filter.key); + optional 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 actual = getValue(filter.key); + optional 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 actual = getValue(filter.key); + optional 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 actual = getValue(filter.key); + optional actual = propertyAccessor(filter.key); if (!actual) return false; for (const auto& v: filter.values) { @@ -73,7 +73,7 @@ public: } bool operator()(const NotInFilter& filter) const { - optional actual = getValue(filter.key); + optional actual = propertyAccessor(filter.key); if (!actual) return true; for (const auto& v: filter.values) { @@ -112,30 +112,76 @@ public: } bool operator()(const HasFilter& filter) const { - return bool(getValue(filter.key)); + return bool(propertyAccessor(filter.key)); } bool operator()(const NotHasFilter& filter) const { - return !getValue(filter.key); + return !propertyAccessor(filter.key); } -private: - optional getValue(const std::string& key_) const { - if (key_ == "$type") { - return optional(uint64_t(featureType)); - } else if (key_ == "$id") { - if (featureIdentifier) { - return FeatureIdentifier::visit(*featureIdentifier, [] (auto id) { - return Value(std::move(id)); - }); - } else { - return optional(); + + 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; } - } else { - return propertyAccessor(key_); } + return true; } + bool operator()(const HasIdentifierFilter&) const { + return bool(featureIdentifier); + } + + bool operator()(const NotHasIdentifierFilter&) const { + return !featureIdentifier; + } + +private: template struct Comparator { const Op& op; diff --git a/mapbox-gl-js b/mapbox-gl-js index cf031a6d07..3c52ddba23 160000 --- a/mapbox-gl-js +++ b/mapbox-gl-js @@ -1 +1 @@ -Subproject commit cf031a6d072bda98b51c57021dfd767eb598ab4c +Subproject commit 3c52ddba23124a1844162a7fc2417ac8671fa911 diff --git a/platform/darwin/src/NSComparisonPredicate+MGLAdditions.mm b/platform/darwin/src/NSComparisonPredicate+MGLAdditions.mm index 295b3b21f8..ac2d598d05 100644 --- a/platform/darwin/src/NSComparisonPredicate+MGLAdditions.mm +++ b/platform/darwin/src/NSComparisonPredicate+MGLAdditions.mm @@ -5,8 +5,7 @@ @implementation NSComparisonPredicate (MGLAdditions) -- (mbgl::style::Filter)mgl_filter -{ +- (mbgl::style::Filter)mgl_filter { NSExpression *leftExpression = self.leftExpression; NSExpression *rightExpression = self.rightExpression; NSExpressionType leftType = leftExpression.expressionType; @@ -18,6 +17,25 @@ mbgl::style::EqualsFilter eqFilter; eqFilter.key = self.mgl_keyPath.UTF8String; eqFilter.value = self.mgl_constantValue; + + // Convert $type == to TypeEqualsFilter. + if (eqFilter.key == "$type") { + mbgl::style::TypeEqualsFilter typeEqFilter; + typeEqFilter.value = self.mgl_featureType; + return typeEqFilter; + } + + // Convert $id == to IdentifierEqualsFilter. + if (eqFilter.key == "$id") { + // Convert $id == nil to NotHasIdentifierFilter. + if (eqFilter.value.is()) { + return mbgl::style::NotHasIdentifierFilter(); + } + + mbgl::style::IdentifierEqualsFilter idEqFilter; + idEqFilter.value = self.mgl_featureIdentifier; + return idEqFilter; + } // Convert == nil to NotHasFilter. if (eqFilter.value.is()) { @@ -32,6 +50,25 @@ mbgl::style::NotEqualsFilter neFilter; neFilter.key = self.mgl_keyPath.UTF8String; neFilter.value = self.mgl_constantValue; + + // Convert $type != to TypeNotEqualsFilter. + if (neFilter.key == "$type") { + mbgl::style::TypeNotEqualsFilter typeNeFilter; + typeNeFilter.value = self.mgl_featureType; + return typeNeFilter; + } + + // Convert $id != to IdentifierNotEqualsFilter. + if (neFilter.key == "$id") { + // Convert $id != nil to HasIdentifierFilter. + if (neFilter.value.is()) { + return mbgl::style::HasIdentifierFilter(); + } + + mbgl::style::IdentifierNotEqualsFilter idNeFilter; + idNeFilter.value = self.mgl_featureIdentifier; + return idNeFilter; + } // Convert != nil to HasFilter. if (neFilter.value.is()) { @@ -103,6 +140,21 @@ [NSException raise:NSInvalidArgumentException format:@"Predicate cannot compare values IN attribute."]; } + + // Convert $type IN to TypeInFilter. + if ([leftExpression.keyPath isEqualToString:@"$type"]) { + mbgl::style::TypeInFilter typeInFilter; + typeInFilter.values = rightExpression.mgl_aggregateFeatureType; + return typeInFilter; + } + + // Convert $id IN to IdentifierInFilter. + if ([leftExpression.keyPath isEqualToString:@"$id"]) { + mbgl::style::IdentifierInFilter idInFilter; + idInFilter.values = rightExpression.mgl_aggregateFeatureIdentifier; + return idInFilter; + } + mbgl::style::InFilter inFilter; inFilter.key = leftExpression.keyPath.UTF8String; inFilter.values = rightExpression.mgl_aggregateMBGLValue; @@ -117,6 +169,21 @@ [NSException raise:NSInvalidArgumentException format:@"Predicate cannot compare attribute CONTAINS values."]; } + + // Convert CONTAINS $type to TypeInFilter. + if ([rightExpression.keyPath isEqualToString:@"$type"]) { + mbgl::style::TypeInFilter typeInFilter; + typeInFilter.values = leftExpression.mgl_aggregateFeatureType; + return typeInFilter; + } + + // Convert CONTAINS $id to IdentifierInFilter. + if ([rightExpression.keyPath isEqualToString:@"$id"]) { + mbgl::style::IdentifierInFilter idInFilter; + idInFilter.values = leftExpression.mgl_aggregateFeatureIdentifier; + return idInFilter; + } + mbgl::style::InFilter inFilter; inFilter.key = rightExpression.keyPath.UTF8String; inFilter.values = leftExpression.mgl_aggregateMBGLValue; @@ -192,4 +259,38 @@ return value; } +- (mbgl::FeatureType)mgl_featureType { + NSExpression *leftExpression = self.leftExpression; + NSExpression *rightExpression = self.rightExpression; + NSExpressionType leftType = leftExpression.expressionType; + NSExpressionType rightType = rightExpression.expressionType; + mbgl::FeatureType type; + if (leftType == NSKeyPathExpressionType && rightType == NSConstantValueExpressionType) { + type = rightExpression.mgl_featureType; + } else if (leftType == NSConstantValueExpressionType && rightType == NSKeyPathExpressionType) { + type = leftExpression.mgl_featureType; + } else { + [NSException raise:NSInvalidArgumentException + format:@"Comparison predicate must compare an attribute (as a key path) to a constant or vice versa."]; + } + return type; +} + +- (mbgl::FeatureIdentifier)mgl_featureIdentifier { + NSExpression *leftExpression = self.leftExpression; + NSExpression *rightExpression = self.rightExpression; + NSExpressionType leftType = leftExpression.expressionType; + NSExpressionType rightType = rightExpression.expressionType; + mbgl::FeatureIdentifier identifier; + if (leftType == NSKeyPathExpressionType && rightType == NSConstantValueExpressionType) { + identifier = rightExpression.mgl_featureIdentifier; + } else if (leftType == NSConstantValueExpressionType && rightType == NSKeyPathExpressionType) { + identifier = leftExpression.mgl_featureIdentifier; + } else { + [NSException raise:NSInvalidArgumentException + format:@"Comparison predicate must compare an attribute (as a key path) to a constant or vice versa."]; + } + return identifier; +} + @end diff --git a/platform/darwin/src/NSExpression+MGLAdditions.h b/platform/darwin/src/NSExpression+MGLAdditions.h index c60d6d78ba..491ed5a536 100644 --- a/platform/darwin/src/NSExpression+MGLAdditions.h +++ b/platform/darwin/src/NSExpression+MGLAdditions.h @@ -8,7 +8,10 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, readonly) mbgl::Value mgl_constantMBGLValue; @property (nonatomic, readonly) std::vector mgl_aggregateMBGLValue; +@property (nonatomic, readonly) mbgl::FeatureType mgl_featureType; +@property (nonatomic, readonly) std::vector mgl_aggregateFeatureType; @property (nonatomic, readonly) mbgl::FeatureIdentifier mgl_featureIdentifier; +@property (nonatomic, readonly) std::vector mgl_aggregateFeatureIdentifier; @end diff --git a/platform/darwin/src/NSExpression+MGLAdditions.mm b/platform/darwin/src/NSExpression+MGLAdditions.mm index f19a41327a..c54102b8c9 100644 --- a/platform/darwin/src/NSExpression+MGLAdditions.mm +++ b/platform/darwin/src/NSExpression+MGLAdditions.mm @@ -63,6 +63,67 @@ return {}; } +- (std::vector)mgl_aggregateFeatureType { + if ([self.constantValue isKindOfClass:[NSArray class]] || [self.constantValue isKindOfClass:[NSSet class]]) { + std::vector convertedValues; + for (id value in self.constantValue) { + NSExpression *expression = value; + if (![expression isKindOfClass:[NSExpression class]]) { + expression = [NSExpression expressionForConstantValue:expression]; + } + convertedValues.push_back(expression.mgl_featureType); + } + return convertedValues; + } + [NSException raise:NSInvalidArgumentException + format:@"Constant value expression must contain an array or set."]; + return {}; +} + +- (mbgl::FeatureType)mgl_featureType { + id value = self.constantValue; + if ([value isKindOfClass:NSString.class]) { + if ([value isEqualToString:@"Point"]) { + return mbgl::FeatureType::Point; + } + if ([value isEqualToString:@"LineString"]) { + return mbgl::FeatureType::LineString; + } + if ([value isEqualToString:@"Polygon"]) { + return mbgl::FeatureType::Polygon; + } + } else if ([value isKindOfClass:NSNumber.class]) { + switch ([value integerValue]) { + case 1: + return mbgl::FeatureType::Point; + case 2: + return mbgl::FeatureType::LineString; + case 3: + return mbgl::FeatureType::Polygon; + default: + break; + } + } + return mbgl::FeatureType::Unknown; +} + +- (std::vector)mgl_aggregateFeatureIdentifier { + if ([self.constantValue isKindOfClass:[NSArray class]] || [self.constantValue isKindOfClass:[NSSet class]]) { + std::vector convertedValues; + for (id value in self.constantValue) { + NSExpression *expression = value; + if (![expression isKindOfClass:[NSExpression class]]) { + expression = [NSExpression expressionForConstantValue:expression]; + } + convertedValues.push_back(expression.mgl_featureIdentifier); + } + return convertedValues; + } + [NSException raise:NSInvalidArgumentException + format:@"Constant value expression must contain an array or set."]; + return {}; +} + - (mbgl::FeatureIdentifier)mgl_featureIdentifier { mbgl::Value mbglValue = self.mgl_constantMBGLValue; diff --git a/platform/darwin/src/NSPredicate+MGLAdditions.mm b/platform/darwin/src/NSPredicate+MGLAdditions.mm index 9fd6639b0a..e0511d8740 100644 --- a/platform/darwin/src/NSPredicate+MGLAdditions.mm +++ b/platform/darwin/src/NSPredicate+MGLAdditions.mm @@ -13,14 +13,41 @@ public: return predicates; } - NSExpression *getValues(std::vector values) { + template + NSExpression *getValues(std::vector values) { NSMutableArray *array = [NSMutableArray arrayWithCapacity:values.size()]; for (auto value : values) { - id constantValue = mbgl::Value::visit(value, ValueEvaluator()); + 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 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; @@ -57,6 +84,38 @@ public: 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); @@ -127,7 +186,14 @@ public: 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"]; + } }; @implementation NSPredicate (MGLAdditions) diff --git a/platform/darwin/test/MGLExpressionTests.mm b/platform/darwin/test/MGLExpressionTests.mm index 00b57c15f0..47315b97d6 100644 --- a/platform/darwin/test/MGLExpressionTests.mm +++ b/platform/darwin/test/MGLExpressionTests.mm @@ -212,4 +212,23 @@ XCTAssertTrue(convertedValue.is()); } +#pragma mark - Feature type tests + +- (void)testFeatureType { + XCTAssertEqual([NSExpression expressionWithFormat:@"'Point'"].mgl_featureType, mbgl::FeatureType::Point); + XCTAssertEqual([NSExpression expressionWithFormat:@"'LineString'"].mgl_featureType, mbgl::FeatureType::LineString); + XCTAssertEqual([NSExpression expressionWithFormat:@"'Polygon'"].mgl_featureType, mbgl::FeatureType::Polygon); + XCTAssertEqual([NSExpression expressionWithFormat:@"'Unknown'"].mgl_featureType, mbgl::FeatureType::Unknown); + XCTAssertEqual([NSExpression expressionWithFormat:@"''"].mgl_featureType, mbgl::FeatureType::Unknown); + + XCTAssertEqual([NSExpression expressionWithFormat:@"1"].mgl_featureType, mbgl::FeatureType::Point); + XCTAssertEqual([NSExpression expressionWithFormat:@"2"].mgl_featureType, mbgl::FeatureType::LineString); + XCTAssertEqual([NSExpression expressionWithFormat:@"3"].mgl_featureType, mbgl::FeatureType::Polygon); + XCTAssertEqual([NSExpression expressionWithFormat:@"0"].mgl_featureType, mbgl::FeatureType::Unknown); + XCTAssertEqual([NSExpression expressionWithFormat:@"-1"].mgl_featureType, mbgl::FeatureType::Unknown); + XCTAssertEqual([NSExpression expressionWithFormat:@"4"].mgl_featureType, mbgl::FeatureType::Unknown); + + XCTAssertEqual([NSExpression expressionWithFormat:@"nil"].mgl_featureType, mbgl::FeatureType::Unknown); +} + @end diff --git a/platform/darwin/test/MGLPredicateTests.mm b/platform/darwin/test/MGLPredicateTests.mm index f34b480a25..6e6951dcdd 100644 --- a/platform/darwin/test/MGLPredicateTests.mm +++ b/platform/darwin/test/MGLPredicateTests.mm @@ -41,12 +41,48 @@ namespace mbgl { mbgl::style::EqualsFilter expected = { .key = "a", .value = std::string("b") }; MGLAssertEqualFilters(actual, expected); } + + { + auto actual = [NSPredicate predicateWithFormat:@"%K = 'Point'", @"$type"].mgl_filter; + mbgl::style::TypeEqualsFilter expected = { .value = mbgl::FeatureType::Point }; + MGLAssertEqualFilters(actual, expected); + } + + { + auto actual = [NSPredicate predicateWithFormat:@"%K = 67086180", @"$id"].mgl_filter; + mbgl::style::IdentifierEqualsFilter expected = { .value = UINT64_C(67086180) }; + MGLAssertEqualFilters(actual, expected); + } + + { + auto actual = [NSPredicate predicateWithFormat:@"%K = nil", @"$id"].mgl_filter; + mbgl::style::NotHasIdentifierFilter expected; + MGLAssertEqualFilters(actual, expected); + } { auto actual = [NSPredicate predicateWithFormat:@"a = nil"].mgl_filter; mbgl::style::NotHasFilter expected = { .key = "a" }; MGLAssertEqualFilters(actual, expected); } + + { + auto actual = [NSPredicate predicateWithFormat:@"%K != 'Point'", @"$type"].mgl_filter; + mbgl::style::TypeNotEqualsFilter expected = { .value = mbgl::FeatureType::Point }; + MGLAssertEqualFilters(actual, expected); + } + + { + auto actual = [NSPredicate predicateWithFormat:@"%K != 67086180", @"$id"].mgl_filter; + mbgl::style::IdentifierNotEqualsFilter expected = { .value = UINT64_C(67086180) }; + MGLAssertEqualFilters(actual, expected); + } + + { + auto actual = [NSPredicate predicateWithFormat:@"%K != nil", @"$id"].mgl_filter; + mbgl::style::HasIdentifierFilter expected; + MGLAssertEqualFilters(actual, expected); + } { auto actual = [NSPredicate predicateWithFormat:@"a != 'b'"].mgl_filter; @@ -117,6 +153,30 @@ namespace mbgl { mbgl::style::InFilter expected = { .key = "a", .values = { std::string("b"), std::string("c") } }; MGLAssertEqualFilters(actual, expected); } + + { + auto actual = [NSPredicate predicateWithFormat:@"%K IN {'LineString', 'Polygon'}", @"$type"].mgl_filter; + mbgl::style::TypeInFilter expected = { .values = { mbgl::FeatureType::LineString, mbgl::FeatureType::Polygon } }; + MGLAssertEqualFilters(actual, expected); + } + + { + auto actual = [NSPredicate predicateWithFormat:@"%K IN %@", @"$type", @[@"LineString", @"Polygon"]].mgl_filter; + mbgl::style::TypeInFilter expected = { .values = { mbgl::FeatureType::LineString, mbgl::FeatureType::Polygon } }; + MGLAssertEqualFilters(actual, expected); + } + + { + auto actual = [NSPredicate predicateWithFormat:@"%K IN {67086180, 3709678893, 3352016856, 4189833989}", @"$id"].mgl_filter; + mbgl::style::IdentifierInFilter expected = { .values = { UINT64_C(67086180), UINT64_C(3709678893), UINT64_C(3352016856), UINT64_C(4189833989) } }; + MGLAssertEqualFilters(actual, expected); + } + + { + auto actual = [NSPredicate predicateWithFormat:@"%K IN %@", @"$id", @[@67086180, @3709678893, @3352016856, @4189833989]].mgl_filter; + mbgl::style::IdentifierInFilter expected = { .values = { UINT64_C(67086180), UINT64_C(3709678893), UINT64_C(3352016856), UINT64_C(4189833989) } }; + MGLAssertEqualFilters(actual, expected); + } XCTAssertThrowsSpecificNamed([NSPredicate predicateWithFormat:@"'Mapbox' IN a"].mgl_filter, NSException, NSInvalidArgumentException); @@ -131,6 +191,24 @@ namespace mbgl { mbgl::style::InFilter expected = { .key = "a", .values = { std::string("b"), std::string("c") } }; MGLAssertEqualFilters(actual, expected); } + + { + auto actual = [NSPredicate predicateWithFormat:@"%@ CONTAINS %K", @[@"LineString", @"Polygon"], @"$type"].mgl_filter; + mbgl::style::TypeInFilter expected = { .values = { mbgl::FeatureType::LineString, mbgl::FeatureType::Polygon } }; + MGLAssertEqualFilters(actual, expected); + } + + { + auto actual = [NSPredicate predicateWithFormat:@"{67086180, 3709678893, 3352016856, 4189833989} CONTAINS %K", @"$id"].mgl_filter; + mbgl::style::IdentifierInFilter expected = { .values = { UINT64_C(67086180), UINT64_C(3709678893), UINT64_C(3352016856), UINT64_C(4189833989) } }; + MGLAssertEqualFilters(actual, expected); + } + + { + auto actual = [NSPredicate predicateWithFormat:@"%@ CONTAINS %K", @[@67086180, @3709678893, @3352016856, @4189833989], @"$id"].mgl_filter; + mbgl::style::IdentifierInFilter expected = { .values = { UINT64_C(67086180), UINT64_C(3709678893), UINT64_C(3352016856), UINT64_C(4189833989) } }; + MGLAssertEqualFilters(actual, expected); + } XCTAssertThrowsSpecificNamed([NSPredicate predicateWithFormat:@"a CONTAINS 'Mapbox'"].mgl_filter, NSException, NSInvalidArgumentException); @@ -238,6 +316,41 @@ namespace mbgl { 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" }; @@ -248,6 +361,24 @@ namespace mbgl { 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" }; @@ -298,11 +429,35 @@ namespace mbgl { 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; diff --git a/platform/ios/CHANGELOG.md b/platform/ios/CHANGELOG.md index 5b4a81c6e3..f7e617b27e 100644 --- a/platform/ios/CHANGELOG.md +++ b/platform/ios/CHANGELOG.md @@ -23,7 +23,8 @@ Mapbox welcomes participation and contributions from everyone. Please read [CONT * Removed support for the `ref` property in layers in style JSON files. ([#7586](https://github.com/mapbox/mapbox-gl-native/pull/7586)) * Fixed an issue that collapsed consecutive newlines within text labels. ([#7446](https://github.com/mapbox/mapbox-gl-native/pull/7446)) * Fixed artifacts when drawing particularly acute line joins. ([#7786](https://github.com/mapbox/mapbox-gl-native/pull/7786)) -* Fixed an issue in which a vector style layer predicate involving the `$id` key path would exclude all features from the layer. ([#7989](https://github.com/mapbox/mapbox-gl-native/pull/7989)) +* Fixed an issue in which a vector style layer predicate involving the `$id` key path would exclude all features from the layer. ([#7989](https://github.com/mapbox/mapbox-gl-native/pull/7989), [#7971](https://github.com/mapbox/mapbox-gl-native/pull/7971)) +* Fixed an issue causing vector style layer predicates to be evaluated as if each feature had a `$type` attribute of 1, 2, or 3. The `$type` key path can now be compared to `Point`, `LineString`, or `Polygon`, as described in the documentation. ([#7971](https://github.com/mapbox/mapbox-gl-native/pull/7971)) ### Networking and offline maps diff --git a/platform/macos/CHANGELOG.md b/platform/macos/CHANGELOG.md index dc2c93a64a..3db58e3047 100644 --- a/platform/macos/CHANGELOG.md +++ b/platform/macos/CHANGELOG.md @@ -21,7 +21,8 @@ * Removed support for the `ref` property in layers in style JSON files. ([#7586](https://github.com/mapbox/mapbox-gl-native/pull/7586)) * Fixed an issue that collapsed consecutive newlines within text labels. ([#7446](https://github.com/mapbox/mapbox-gl-native/pull/7446)) * Fixed artifacts when drawing particularly acute line joins. ([#7786](https://github.com/mapbox/mapbox-gl-native/pull/7786)) -* Fixed an issue in which a vector style layer predicate involving the `$id` key path would exclude all features from the layer. ([#7989](https://github.com/mapbox/mapbox-gl-native/pull/7989)) +* Fixed an issue in which a vector style layer predicate involving the `$id` key path would exclude all features from the layer. ([#7989](https://github.com/mapbox/mapbox-gl-native/pull/7989), [#7971](https://github.com/mapbox/mapbox-gl-native/pull/7971)) +* Fixed an issue causing vector style layer predicates to be evaluated as if each feature had a `$type` attribute of 1, 2, or 3. The `$type` key path can now be compared to `Point`, `LineString`, or `Polygon`, as described in the documentation. ([#7971](https://github.com/mapbox/mapbox-gl-native/pull/7971)) ### Networking and offline maps diff --git a/src/mbgl/style/conversion/stringify.hpp b/src/mbgl/style/conversion/stringify.hpp index 4b9cf6d26e..4afbf198e5 100644 --- a/src/mbgl/style/conversion/stringify.hpp +++ b/src/mbgl/style/conversion/stringify.hpp @@ -102,6 +102,29 @@ void stringify(Writer& writer, const Value& v) { Value::visit(v, [&] (const auto& v_) { stringify(writer, v_); }); } +template +void stringify(Writer& writer, FeatureType type) { + switch (type) { + case FeatureType::Unknown: + writer.String("Unknown"); + break; + case FeatureType::Point: + writer.String("Point"); + break; + case FeatureType::LineString: + writer.String("LineString"); + break; + case FeatureType::Polygon: + writer.String("Polygon"); + break; + } +} + +template +void stringify(Writer& writer, const FeatureIdentifier& id) { + FeatureIdentifier::visit(id, [&] (const auto& id_) { stringify(writer, id_); }); +} + template class StringifyFilter { public: @@ -156,28 +179,78 @@ public: } void operator()(const HasFilter& f) { - stringifyUnaryFilter(f, "has"); + stringifyUnaryFilter("has", f.key); } void operator()(const NotHasFilter& f) { - stringifyUnaryFilter(f, "!has"); + 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"); } private: template void stringifyBinaryFilter(const F& f, const char * op) { + stringifyBinaryFilter(f, op, f.key); + } + + template + void stringifyBinaryFilter(const F& f, const char * op, const std::string& key) { writer.StartArray(); writer.String(op); - writer.String(f.key); + writer.String(key); stringify(writer, f.value); writer.EndArray(); } template void stringifySetFilter(const F& f, const char * op) { + stringifySetFilter(f, op, f.key); + } + + template + void stringifySetFilter(const F& f, const char * op, const std::string& key) { writer.StartArray(); writer.String(op); - writer.String(f.key); + writer.String(key); for (const auto& value : f.values) { stringify(writer, value); } @@ -194,11 +267,10 @@ private: writer.EndArray(); } - template - void stringifyUnaryFilter(const F& f, const char * op) { + void stringifyUnaryFilter(const char * op, const std::string& key) { writer.StartArray(); writer.String(op); - writer.String(f.key); + writer.String(key); writer.EndArray(); } }; -- cgit v1.2.1