diff --git a/benchmark/parse/filter.cpp b/benchmark/parse/filter.cpp index 8beff3b30..4269f8e16 100644 --- a/benchmark/parse/filter.cpp +++ b/benchmark/parse/filter.cpp @@ -2,7 +2,8 @@ #include #include -#include +#include +#include #include #include @@ -16,7 +17,7 @@ typedef std::multimap Properties; style::Filter parse(const char* expression) { rapidjson::GenericDocument, rapidjson::CrtAllocator> doc; doc.Parse<0>(expression); - return style::parseFilter(doc); + return style::conversion::convertFilter(doc).get(); } static void Parse_Filter(benchmark::State& state) { diff --git a/include/mbgl/style/conversion.hpp b/include/mbgl/style/conversion.hpp index 0507d452f..18326c8dd 100644 --- a/include/mbgl/style/conversion.hpp +++ b/include/mbgl/style/conversion.hpp @@ -2,7 +2,7 @@ #include #include -#include +#include #include #include @@ -27,6 +27,12 @@ namespace conversion { This is used concretely for conversions from RapidJSON types in mbgl core, and from v8 types in the node bindings. + It also defines a convertion from `V` to `Filter`, representing a JSON object conforming to a Style + Specification filter object: + + template + Result convertFilter(const V& value); + The requirements are that the following are legal expressions for a value `v` of type `const V&`: * `isArray(v)` -- returns a boolean indicating whether `v` represents a JSON array @@ -41,6 +47,9 @@ namespace conversion { * `toBool(v)` -- returns `optional`, absence indicating `v` is not a JSON boolean * `toNumber(v)` -- returns `optional`, absence indicating `v` is not a JSON number * `toString(v)` -- returns `optional`, absence indicating `v` is not a JSON string + * `toValue(v)` -- returns `optional`, a variant type, for generic conversion, + absence indicating `v` is not a boolean, number, or string. Numbers should be converted to + unsigned integer, signed integer, or floating point, in descending preference. If for any reason the conversion fails, the result of `convertPropertyValue` will be the `Error` variant, which includes explanatory text. @@ -258,6 +267,127 @@ Result> convertPropertyValue(const V& value) { return Function(stops, *base); } +Result normalizeFilterValue(const std::string& key, const optional&); + +template +Result convertFilter(const V& value); + +template +Result parseUnaryFilter(const V& value) { + if (arrayLength(value) < 2) { + return Error { "filter expression must have 2 elements" }; + } + + optional key = toString(arrayMember(value, 1)); + if (!key) { + return Error { "filter expression key must be a string" }; + } + + return FilterType { *key }; +} + +template +Result parseBinaryFilter(const V& value) { + 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" }; + } + + Result filterValue = normalizeFilterValue(*key, toValue(arrayMember(value, 2))); + if (filterValue.is()) { + return filterValue.get(); + } + + return FilterType { *key, filterValue.get() }; +} + +template +Result parseSetFilter(const V& value) { + if (arrayLength(value) < 2) { + return Error { "filter expression must at least 2 elements" }; + } + + optional key = toString(arrayMember(value, 1)); + if (!key) { + return Error { "filter expression key must be a string" }; + } + + std::vector values; + for (std::size_t i = 2; i < arrayLength(value); ++i) { + Result filterValue = normalizeFilterValue(*key, toValue(arrayMember(value, i))); + if (filterValue.is()) { + return filterValue.get(); + } + values.push_back(filterValue.get()); + } + + return FilterType { *key, std::move(values) }; +} + +template +Result parseCompoundFilter(const V& value) { + std::vector filters; + for (std::size_t i = 1; i < arrayLength(value); ++i) { + Result element = convertFilter(arrayMember(value, i)); + if (element.is()) { + return element; + } + filters.push_back(element.get()); + } + + return FilterType { std::move(filters) }; +} + +template +Result convertFilter(const V& value) { + if (!isArray(value)) { + return Error { "filter expression must be an array" }; + } + + if (arrayLength(value) < 1) { + return Error { "filter expression must have at least 1 element" }; + } + + optional op = toString(arrayMember(value, 0)); + if (!op) { + return Error { "filter operator must be a string" }; + } + + if (*op == "==") { + return parseBinaryFilter(value); + } else if (*op == "!=") { + return parseBinaryFilter(value); + } else if (*op == ">") { + return parseBinaryFilter(value); + } else if (*op == ">=") { + return parseBinaryFilter(value); + } else if (*op == "<") { + return parseBinaryFilter(value); + } else if (*op == "<=") { + return parseBinaryFilter(value); + } else if (*op == "in") { + return parseSetFilter(value); + } else if (*op == "!in") { + return parseSetFilter(value); + } else if (*op == "all") { + return parseCompoundFilter(value); + } else if (*op == "any") { + return parseCompoundFilter(value); + } else if (*op == "none") { + return parseCompoundFilter(value); + } else if (*op == "has") { + return parseUnaryFilter(value); + } else if (*op == "!has") { + return parseUnaryFilter(value); + } + + return Error { "filter operator must be one of \"==\", \"!=\", \">\", \">=\", \"<\", \"<=\", \"in\", \"!in\", \"all\", \"any\", \"none\", \"has\", or \"!has\"" }; +} + } // namespace conversion } // namespace style } // namespace mbgl diff --git a/package.json b/package.json index 053083fb0..dba546af4 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "express": "^4.11.1", "mapbox-gl-shaders": "mapbox/mapbox-gl-shaders#59e998295d548f208ee3ec10cdd21ff2630e2079", "mapbox-gl-style-spec": "mapbox/mapbox-gl-style-spec#194fc55b6a7dd54c1e2cf2dd9048fbb5e836716d", - "mapbox-gl-test-suite": "mapbox/mapbox-gl-test-suite#87d237a9d7372325fffd0c04036b2d77e601e72b", + "mapbox-gl-test-suite": "mapbox/mapbox-gl-test-suite#47bbd92debe0f72ec9759d2e12d9c8d4a57232c5", "node-gyp": "^3.3.1", "request": "^2.72.0", "tape": "^4.5.1" diff --git a/platform/node/src/node_conversion.hpp b/platform/node/src/node_conversion.hpp index 51443f28e..03c13b584 100644 --- a/platform/node/src/node_conversion.hpp +++ b/platform/node/src/node_conversion.hpp @@ -7,6 +7,7 @@ #pragma GCC diagnostic pop #include +#include namespace mbgl { namespace style { @@ -68,6 +69,24 @@ inline optional toString(v8::Local value) { return std::string(*Nan::Utf8String(value)); } +inline optional toValue(v8::Local value) { + if (value->IsFalse()) { + return { false }; + } else if (value->IsTrue()) { + return { true }; + } else if (value->IsString()) { + return { std::string(*Nan::Utf8String(value)) }; + } else if (value->IsUint32()) { + return { value->Uint32Value() }; + } else if (value->IsInt32()) { + return { value->Int32Value() }; + } else if (value->IsNumber()) { + return { value->NumberValue() }; + } else { + return {}; + } +} + } // namespace conversion } // namespace style } // namespace mbgl diff --git a/platform/node/src/node_map.cpp b/platform/node/src/node_map.cpp index c416082b6..e6ee84ee5 100644 --- a/platform/node/src/node_map.cpp +++ b/platform/node/src/node_map.cpp @@ -58,6 +58,7 @@ NAN_MODULE_INIT(NodeMap::Init) { Nan::SetPrototypeMethod(tpl, "addClass", AddClass); Nan::SetPrototypeMethod(tpl, "setLayoutProperty", SetLayoutProperty); Nan::SetPrototypeMethod(tpl, "setPaintProperty", SetPaintProperty); + Nan::SetPrototypeMethod(tpl, "setFilter", SetFilter); Nan::SetPrototypeMethod(tpl, "dumpDebugLogs", DumpDebugLogs); Nan::SetPrototypeMethod(tpl, "queryRenderedFeatures", QueryRenderedFeatures); @@ -526,6 +527,42 @@ NAN_METHOD(NodeMap::SetPaintProperty) { setProperty(info, setters); } +NAN_METHOD(NodeMap::SetFilter) { + auto nodeMap = Nan::ObjectWrap::Unwrap(info.Holder()); + if (!nodeMap->map) return Nan::ThrowError(releasedMessage()); + + if (info.Length() < 2) { + return Nan::ThrowTypeError("Two arguments required"); + } + + if (!info[0]->IsString()) { + return Nan::ThrowTypeError("First argument must be a string"); + } + + mbgl::style::Layer* layer = nodeMap->map->getLayer(*Nan::Utf8String(info[0])); + if (!layer) { + return Nan::ThrowTypeError("layer not found"); + } + + mbgl::style::Filter filter; + + if (!info[1]->IsNull() && !info[1]->IsUndefined()) { + mbgl::style::conversion::Result converted + = mbgl::style::conversion::convertFilter(info[1]); + if (converted.is()) { + Nan::ThrowTypeError(converted.get().message); + return; + } + filter = std::move(converted.get()); + } + + if (!setFilter(*layer, filter)) { + return; + } + + info.GetReturnValue().SetUndefined(); +} + NAN_METHOD(NodeMap::DumpDebugLogs) { auto nodeMap = Nan::ObjectWrap::Unwrap(info.Holder()); if (!nodeMap->map) return Nan::ThrowError(releasedMessage()); diff --git a/platform/node/src/node_map.hpp b/platform/node/src/node_map.hpp index fb7fc77e5..58773e33d 100644 --- a/platform/node/src/node_map.hpp +++ b/platform/node/src/node_map.hpp @@ -30,6 +30,7 @@ public: static NAN_METHOD(AddClass); static NAN_METHOD(SetLayoutProperty); static NAN_METHOD(SetPaintProperty); + static NAN_METHOD(SetFilter); static NAN_METHOD(DumpDebugLogs); static NAN_METHOD(QueryRenderedFeatures); diff --git a/platform/node/src/node_style_properties.hpp b/platform/node/src/node_style_properties.hpp index 1ad06068f..0e3100207 100644 --- a/platform/node/src/node_style_properties.hpp +++ b/platform/node/src/node_style_properties.hpp @@ -122,4 +122,28 @@ inline PropertySetters makePaintPropertySetters() { return result; } +inline bool setFilter(mbgl::style::Layer& layer, const mbgl::style::Filter& filter) { + using namespace mbgl::style; + + if (layer.is()) { + layer.as()->setFilter(filter); + return true; + } + if (layer.is()) { + layer.as()->setFilter(filter); + return true; + } + if (layer.is()) { + layer.as()->setFilter(filter); + return true; + } + if (layer.is()) { + layer.as()->setFilter(filter); + return true; + } + + Nan::ThrowTypeError("layer doesn't support filters"); + return false; +} + } diff --git a/platform/node/src/node_style_properties.hpp.ejs b/platform/node/src/node_style_properties.hpp.ejs index ebf45eb3c..bfd3f74ca 100644 --- a/platform/node/src/node_style_properties.hpp.ejs +++ b/platform/node/src/node_style_properties.hpp.ejs @@ -34,4 +34,20 @@ inline PropertySetters makePaintPropertySetters() { return result; } +inline bool setFilter(mbgl::style::Layer& layer, const mbgl::style::Filter& filter) { + using namespace mbgl::style; + +<% for (const layer of locals.layers) { -%> +<% if (layer.type !== 'raster' && layer.type !== 'background' && layer.type !== 'custom') { -%> + if (layer.is<<%- camelize(layer.type) %>Layer>()) { + layer.as<<%- camelize(layer.type) %>Layer>()->setFilter(filter); + return true; + } +<% } -%> +<% } -%> + + Nan::ThrowTypeError("layer doesn't support filters"); + return false; +} + } diff --git a/src/mbgl/style/conversion.cpp b/src/mbgl/style/conversion.cpp new file mode 100644 index 000000000..e863e285c --- /dev/null +++ b/src/mbgl/style/conversion.cpp @@ -0,0 +1,25 @@ +#include + +namespace mbgl { +namespace style { +namespace conversion { + +Result normalizeFilterValue(const std::string& key, const optional& value) { + if (!value) { + return Error { "filter expression value must be a boolean, number, or string" }; + } else if (key != "$type") { + 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)); + } else { + return Error { "value for $type filter must be Point, LineString, or Polygon" }; + } +} + +} // namespace conversion +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/parser.cpp b/src/mbgl/style/parser.cpp index 34f01c14c..eddda5d3f 100644 --- a/src/mbgl/style/parser.cpp +++ b/src/mbgl/style/parser.cpp @@ -9,6 +9,8 @@ #include #include #include +#include +#include #include @@ -275,7 +277,12 @@ void Parser::parseLayer(const std::string& id, const JSValue& value, std::unique } if (value.HasMember("filter")) { - impl->filter = parseFilter(value["filter"]); + conversion::Result filter = conversion::convertFilter(value["filter"]); + if (filter.is()) { + impl->filter = filter.get(); + } else { + Log::Warning(Event::ParseStyle, filter.get().message); + } } if (value.HasMember("minzoom")) { @@ -326,172 +333,6 @@ void Parser::parseVisibility(Layer& layer, const JSValue& value) { impl.visibility = *enumValue; } -Value parseFeatureType(const Value& value) { - 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)); - } else { - Log::Warning(Event::ParseStyle, "value for $type filter must be Point, LineString, or Polygon"); - return Value(uint64_t(FeatureType::Unknown)); - } -} - -Value parseValue(const JSValue& value) { - switch (value.GetType()) { - case rapidjson::kNullType: - case rapidjson::kFalseType: - return false; - - case rapidjson::kTrueType: - return true; - - case rapidjson::kStringType: - return std::string { value.GetString(), value.GetStringLength() }; - - case rapidjson::kNumberType: - if (value.IsUint64()) return value.GetUint64(); - if (value.IsInt64()) return value.GetInt64(); - return value.GetDouble(); - - default: - return false; - } -} - -template -Filter parseUnaryFilter(const JSValue& value) { - Filter empty; - - if (value.Size() < 2) { - Log::Warning(Event::ParseStyle, "filter expression must have 2 elements"); - return empty; - } - - if (!value[1u].IsString()) { - Log::Warning(Event::ParseStyle, "filter expression key must be a string"); - return empty; - } - - Expression expression; - expression.key = { value[1u].GetString(), value[1u].GetStringLength() }; - return expression; -} - -template -Filter parseBinaryFilter(const JSValue& value) { - Filter empty; - - if (value.Size() < 3) { - Log::Warning(Event::ParseStyle, "filter expression must have 3 elements"); - return empty; - } - - if (!value[1u].IsString()) { - Log::Warning(Event::ParseStyle, "filter expression key must be a string"); - return empty; - } - - Expression expression; - expression.key = { value[1u].GetString(), value[1u].GetStringLength() }; - expression.value = parseValue(value[2u]); - - if (expression.key == "$type") { - expression.value = parseFeatureType(expression.value); - } - - return expression; -} - -template -Filter parseSetFilter(const JSValue& value) { - Filter empty; - - if (value.Size() < 2) { - Log::Warning(Event::ParseStyle, "filter expression must at least 2 elements"); - return empty; - } - - if (!value[1u].IsString()) { - Log::Warning(Event::ParseStyle, "filter expression key must be a string"); - return empty; - } - - Expression expression; - expression.key = { value[1u].GetString(), value[1u].GetStringLength() }; - for (rapidjson::SizeType i = 2; i < value.Size(); ++i) { - Value parsedValue = parseValue(value[i]); - if (expression.key == "$type") { - parsedValue = parseFeatureType(parsedValue); - } - expression.values.push_back(parsedValue); - } - return expression; -} - -template -Filter parseCompoundFilter(const JSValue& value) { - Expression expression; - for (rapidjson::SizeType i = 1; i < value.Size(); ++i) { - expression.filters.push_back(parseFilter(value[i])); - } - return expression; -} - -Filter parseFilter(const JSValue& value) { - Filter empty; - - if (!value.IsArray()) { - Log::Warning(Event::ParseStyle, "filter expression must be an array"); - return empty; - } - - if (value.Size() < 1) { - Log::Warning(Event::ParseStyle, "filter expression must have at least 1 element"); - return empty; - } - - if (!value[0u].IsString()) { - Log::Warning(Event::ParseStyle, "filter operator must be a string"); - return empty; - } - - std::string op = { value[0u].GetString(), value[0u].GetStringLength() }; - - if (op == "==") { - return parseBinaryFilter(value); - } else if (op == "!=") { - return parseBinaryFilter(value); - } else if (op == ">") { - return parseBinaryFilter(value); - } else if (op == ">=") { - return parseBinaryFilter(value); - } else if (op == "<") { - return parseBinaryFilter(value); - } else if (op == "<=") { - return parseBinaryFilter(value); - } else if (op == "in") { - return parseSetFilter(value); - } else if (op == "!in") { - return parseSetFilter(value); - } else if (op == "all") { - return parseCompoundFilter(value); - } else if (op == "any") { - return parseCompoundFilter(value); - } else if (op == "none") { - return parseCompoundFilter(value); - } else if (op == "has") { - return parseUnaryFilter(value); - } else if (op == "!has") { - return parseUnaryFilter(value); - } else { - Log::Warning(Event::ParseStyle, "filter operator must be one of \"==\", \"!=\", \">\", \">=\", \"<\", \"<=\", \"in\", \"!in\", \"all\", \"any\", \"none\", \"has\", or \"!has\""); - return empty; - } -} - std::vector Parser::fontStacks() const { std::set result; diff --git a/src/mbgl/style/parser.hpp b/src/mbgl/style/parser.hpp index 6dac6dedc..30da0dd2b 100644 --- a/src/mbgl/style/parser.hpp +++ b/src/mbgl/style/parser.hpp @@ -2,7 +2,6 @@ #include #include -#include #include #include @@ -16,8 +15,6 @@ namespace mbgl { namespace style { -Filter parseFilter(const JSValue&); - class Parser { public: ~Parser(); diff --git a/src/mbgl/style/rapidjson_conversion.hpp b/src/mbgl/style/rapidjson_conversion.hpp index 93577ac8b..1d9f88efa 100644 --- a/src/mbgl/style/rapidjson_conversion.hpp +++ b/src/mbgl/style/rapidjson_conversion.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include namespace mbgl { namespace style { @@ -50,6 +51,28 @@ inline optional toString(const JSValue& value) { return {{ value.GetString(), value.GetStringLength() }}; } +inline optional toValue(const JSValue& value) { + switch (value.GetType()) { + case rapidjson::kNullType: + case rapidjson::kFalseType: + return { false }; + + case rapidjson::kTrueType: + return { true }; + + case rapidjson::kStringType: + return { std::string { value.GetString(), value.GetStringLength() } }; + + case rapidjson::kNumberType: + if (value.IsUint64()) return { value.GetUint64() }; + if (value.IsInt64()) return { value.GetInt64() }; + return { value.GetDouble() }; + + default: + return {}; + } +} + } // namespace conversion } // namespace style } // namespace mbgl diff --git a/test/style/filter.cpp b/test/style/filter.cpp index 4a1010445..fd763896a 100644 --- a/test/style/filter.cpp +++ b/test/style/filter.cpp @@ -2,7 +2,8 @@ #include #include -#include +#include +#include #include @@ -16,7 +17,7 @@ typedef std::multimap Properties; Filter parse(const char * expression) { rapidjson::GenericDocument, rapidjson::CrtAllocator> doc; doc.Parse<0>(expression); - return parseFilter(doc); + return conversion::convertFilter(doc).get(); } bool evaluate(const Filter& filter, const Properties& properties, FeatureType type = FeatureType::Unknown) {