implement nested expression parsing
This commit is contained in:
@@ -263,8 +263,11 @@ module.exports = {
|
||||
"contour_label": {
|
||||
"source": "outdoors",
|
||||
"layer": "contour",
|
||||
"field": "index",
|
||||
"value": [5, 10],
|
||||
"filter": [
|
||||
"and",
|
||||
{ "field": "index", "value": [ 5, 10 ] },
|
||||
{ "field": "ele", "operator": "not", "value": 0 }
|
||||
],
|
||||
"path": "curve",
|
||||
"text_field": "{{ele}} m",
|
||||
"font": "Open Sans Regular, Arial Unicode MS Regular",
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <llmr/util/vec.hpp>
|
||||
#include <llmr/style/value.hpp>
|
||||
#include <llmr/text/glyph.hpp>
|
||||
#include <llmr/map/filter_expression.hpp>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
@@ -14,8 +15,6 @@
|
||||
namespace llmr {
|
||||
|
||||
class BucketDescription;
|
||||
class BucketExpression;
|
||||
class BucketFilter;
|
||||
class VectorTileLayer;
|
||||
|
||||
struct pbf;
|
||||
@@ -56,8 +55,9 @@ public:
|
||||
const pbf& operator*() const;
|
||||
|
||||
private:
|
||||
bool matchesExpression(const BucketExpression &expression, const pbf &tags_pbf);
|
||||
bool matchesFilter(const BucketFilter &filter, const pbf &tags_pbf);
|
||||
bool matchesFilterExpression(const PropertyFilterExpression &filterExpression, const pbf &tags_pbf);
|
||||
bool matchesExpression(const PropertyExpression &expression, const pbf &tags_pbf);
|
||||
bool matchesFilter(const PropertyFilter &filter, const pbf &tags_pbf);
|
||||
|
||||
private:
|
||||
const FilteredVectorTileLayer& parent;
|
||||
|
||||
@@ -5,8 +5,9 @@
|
||||
#include <vector>
|
||||
#include <cmath>
|
||||
|
||||
#include "value.hpp"
|
||||
#include "../util/vec.hpp"
|
||||
#include <llmr/util/vec.hpp>
|
||||
#include <llmr/map/filter_expression.hpp>
|
||||
#include <llmr/style/value.hpp>
|
||||
|
||||
namespace llmr {
|
||||
|
||||
@@ -38,16 +39,6 @@ enum class TextPathType {
|
||||
Curve = 1
|
||||
};
|
||||
|
||||
enum class FilterOperator {
|
||||
Equal,
|
||||
NotEqual
|
||||
};
|
||||
|
||||
enum class ExpressionOperator {
|
||||
Or,
|
||||
And
|
||||
};
|
||||
|
||||
|
||||
inline BucketType bucketType(const std::string& type) {
|
||||
if (type == "fill") return BucketType::Fill;
|
||||
@@ -91,22 +82,6 @@ inline float verticalAlignmentType(const std::string& alignment) {
|
||||
else return 0.5;
|
||||
};
|
||||
|
||||
inline FilterOperator filterOperatorType(const std::string &op) {
|
||||
if (op == "!=" || op == "not") {
|
||||
return FilterOperator::NotEqual;
|
||||
} else {
|
||||
return FilterOperator::Equal;
|
||||
}
|
||||
}
|
||||
|
||||
inline ExpressionOperator expressionOperatorType(const std::string &op) {
|
||||
if (op == "&&" || op == "and") {
|
||||
return ExpressionOperator::And;
|
||||
} else {
|
||||
return ExpressionOperator::Or;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class BucketGeometryDescription {
|
||||
public:
|
||||
@@ -133,41 +108,6 @@ public:
|
||||
};
|
||||
|
||||
|
||||
class BucketFilter {
|
||||
public:
|
||||
inline BucketFilter(const std::string &field, const Value &value) : field(field), value(value) {};
|
||||
|
||||
// Returns true if the filter passes, even if the key is missing.
|
||||
inline bool isMissingFieldOkay() const {
|
||||
switch (op) {
|
||||
case FilterOperator::NotEqual:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
inline bool compare(const Value &other) const {
|
||||
switch (op) {
|
||||
case FilterOperator::NotEqual:
|
||||
return !util::relaxed_equal(value)(other);
|
||||
default:
|
||||
return util::relaxed_equal(value)(other);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
std::string field;
|
||||
Value value;
|
||||
FilterOperator op = FilterOperator::Equal;
|
||||
};
|
||||
|
||||
class BucketExpression {
|
||||
public:
|
||||
ExpressionOperator op = ExpressionOperator::Or;
|
||||
std::vector<BucketFilter> operands;
|
||||
};
|
||||
|
||||
class BucketDescription {
|
||||
public:
|
||||
BucketType feature_type = BucketType::None;
|
||||
@@ -177,7 +117,7 @@ public:
|
||||
std::string source_name;
|
||||
std::string source_layer;
|
||||
|
||||
BucketExpression filter;
|
||||
PropertyFilterExpression filter;
|
||||
|
||||
// Specifies how the geometry for this bucket should be created
|
||||
BucketGeometryDescription geometry;
|
||||
|
||||
@@ -39,6 +39,8 @@ private:
|
||||
RasterClass parseRasterClass(JSVal value);
|
||||
CompositeClass parseCompositeClass(JSVal value);
|
||||
|
||||
PropertyFilterExpression parseFilterOrExpression(JSVal value);
|
||||
|
||||
bool parseBoolean(JSVal value);
|
||||
std::string parseString(JSVal value);
|
||||
JSVal replaceConstant(JSVal value);
|
||||
|
||||
@@ -122,9 +122,18 @@ FilteredVectorTileLayer::iterator::iterator(const FilteredVectorTileLayer& paren
|
||||
operator++();
|
||||
}
|
||||
|
||||
bool FilteredVectorTileLayer::iterator::matchesFilter(const BucketFilter &filter, const pbf &const_tags_pbf) {
|
||||
bool missing = true;
|
||||
bool FilteredVectorTileLayer::iterator::matchesFilterExpression(const PropertyFilterExpression &filterExpression, const pbf &tags_pbf) {
|
||||
if (filterExpression.is<PropertyFilter>()) {
|
||||
return matchesFilter(filterExpression.get<PropertyFilter>(), tags_pbf);
|
||||
} else if (filterExpression.is<util::recursive_wrapper<PropertyExpression>>()) {
|
||||
return matchesExpression(filterExpression.get<util::recursive_wrapper<PropertyExpression>>().get(), tags_pbf);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool FilteredVectorTileLayer::iterator::matchesFilter(const PropertyFilter &filter, const pbf &const_tags_pbf) {
|
||||
auto field_it = parent.layer.key_index.find(filter.field);
|
||||
if (field_it != parent.layer.key_index.end()) {
|
||||
const uint32_t filter_key = field_it->second;
|
||||
@@ -157,17 +166,17 @@ bool FilteredVectorTileLayer::iterator::matchesFilter(const BucketFilter &filter
|
||||
return filter.isMissingFieldOkay();
|
||||
}
|
||||
|
||||
bool FilteredVectorTileLayer::iterator::matchesExpression(const BucketExpression &expression, const pbf &tags_pbf) {
|
||||
bool FilteredVectorTileLayer::iterator::matchesExpression(const PropertyExpression &expression, const pbf &tags_pbf) {
|
||||
if (expression.op == ExpressionOperator::Or) {
|
||||
for (const BucketFilter &filter : expression.operands) {
|
||||
if (matchesFilter(filter, tags_pbf)) {
|
||||
for (const PropertyFilterExpression &filterExpression : expression.operands) {
|
||||
if (matchesFilterExpression(filterExpression, tags_pbf)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} else if (expression.op == ExpressionOperator::And) {
|
||||
for (const BucketFilter &filter : expression.operands) {
|
||||
if (!matchesFilter(filter, tags_pbf)) {
|
||||
for (const PropertyFilterExpression &filterExpression : expression.operands) {
|
||||
if (!matchesFilterExpression(filterExpression, tags_pbf)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -182,7 +191,7 @@ bool FilteredVectorTileLayer::iterator::matchesExpression(const BucketExpression
|
||||
void FilteredVectorTileLayer::iterator::operator++() {
|
||||
valid = false;
|
||||
|
||||
BucketExpression expression = parent.bucket_desc.filter;
|
||||
const PropertyFilterExpression &expression = parent.bucket_desc.filter;
|
||||
|
||||
while (data.next(2)) { // feature
|
||||
feature = data.message();
|
||||
@@ -192,7 +201,7 @@ void FilteredVectorTileLayer::iterator::operator++() {
|
||||
bool matched = false;
|
||||
|
||||
// Treat the absence of any expression filters as a match.
|
||||
if (expression.operands.size() == 0) {
|
||||
if (expression.is<util::recursive_wrapper<PropertyExpression>>() && expression.get<util::recursive_wrapper<PropertyExpression>>().get().operands.size() == 0) {
|
||||
matched = true;
|
||||
}
|
||||
|
||||
@@ -204,7 +213,7 @@ void FilteredVectorTileLayer::iterator::operator++() {
|
||||
} else {
|
||||
// We only want to parse some features.
|
||||
const pbf tags_pbf = feature_pbf.message();
|
||||
matched = matchesExpression(expression, tags_pbf);
|
||||
matched = matchesFilterExpression(expression, tags_pbf);
|
||||
if (!matched) {
|
||||
break; // feature_pbf loop early
|
||||
}
|
||||
|
||||
@@ -20,6 +20,56 @@ void StyleParser::parseBuckets(JSVal value, std::map<std::string, BucketDescript
|
||||
}
|
||||
}
|
||||
|
||||
PropertyFilterExpression StyleParser::parseFilterOrExpression(JSVal value) {
|
||||
if (value.IsArray()) {
|
||||
// This is an expression.
|
||||
PropertyExpression expression;
|
||||
for (rapidjson::SizeType i = 0; i < value.Size(); ++i) {
|
||||
JSVal filter_item = value[i];
|
||||
|
||||
if (filter_item.IsString()) {
|
||||
expression.op = expressionOperatorType({ filter_item.GetString(), filter_item.GetStringLength() });
|
||||
} else {
|
||||
expression.operands.emplace_back(parseFilterOrExpression(filter_item));
|
||||
}
|
||||
}
|
||||
return util::recursive_wrapper<PropertyExpression>(std::move(expression));
|
||||
} else if (value.IsObject() && value.HasMember("field") && value.HasMember("value")) {
|
||||
// This is a filter.
|
||||
JSVal field = value["field"];
|
||||
JSVal val = value["value"];
|
||||
|
||||
if (!field.IsString()) {
|
||||
throw Style::exception("field name must be a string");
|
||||
}
|
||||
|
||||
const std::string field_name { field.GetString(), field.GetStringLength() };
|
||||
const FilterOperator op = [&]{
|
||||
if (value.HasMember("operator")) {
|
||||
JSVal op_val = value["operator"];
|
||||
return filterOperatorType({ op_val.GetString(), op_val.GetStringLength() });
|
||||
} else {
|
||||
return FilterOperator::Equal;
|
||||
}
|
||||
}();
|
||||
|
||||
if (val.IsArray()) {
|
||||
// The filter has several values, so it's an OR sub-expression.
|
||||
PropertyExpression expression;
|
||||
for (rapidjson::SizeType i = 0; i < val.Size(); ++i) {
|
||||
expression.operands.emplace_back(PropertyFilter { field_name, op, parseValue(val[i]) });
|
||||
}
|
||||
|
||||
return util::recursive_wrapper<PropertyExpression>(std::move(expression));
|
||||
} else {
|
||||
// The filter only has a single value, so it is a real filter.
|
||||
return PropertyFilter { field_name, op, parseValue(val) };
|
||||
}
|
||||
}
|
||||
|
||||
return util::recursive_wrapper<PropertyExpression>();
|
||||
}
|
||||
|
||||
BucketDescription StyleParser::parseBucket(JSVal value) {
|
||||
BucketDescription bucket;
|
||||
|
||||
@@ -153,26 +203,11 @@ BucketDescription StyleParser::parseBucket(JSVal value) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
if (value.HasMember("field") && value.HasMember("value")) {
|
||||
JSVal field = value["field"];
|
||||
JSVal val = value["value"];
|
||||
|
||||
if (!field.IsString()) {
|
||||
throw Style::exception("field name must be a string");
|
||||
}
|
||||
|
||||
const std::string field_name { field.GetString(), field.GetStringLength() };
|
||||
|
||||
bucket.filter = {};
|
||||
|
||||
if (val.IsArray()) {
|
||||
for (rapidjson::SizeType i = 0; i < val.Size(); ++i) {
|
||||
bucket.filter.operands.emplace_back(field_name, parseValue(val[i]));
|
||||
}
|
||||
} else {
|
||||
bucket.filter.operands.emplace_back(field_name, parseValue(val));
|
||||
}
|
||||
if (value.HasMember("filter")) {
|
||||
bucket.filter = std::move(parseFilterOrExpression(value["filter"]));
|
||||
} else {
|
||||
// Allow a filter triple to be specified at the bucket root as well.
|
||||
bucket.filter = std::move(parseFilterOrExpression(value));
|
||||
}
|
||||
|
||||
if (bucket.feature_type == BucketType::None) {
|
||||
|
||||
Reference in New Issue
Block a user