[core, node] Node bindings for setFilter

This commit is contained in:
John Firebaugh
2016-06-21 17:22:51 -07:00
parent 220fc7adc9
commit 3459b83736
13 changed files with 291 additions and 176 deletions

View File

@@ -2,7 +2,8 @@
#include <mbgl/style/filter.hpp>
#include <mbgl/style/filter_evaluator.hpp>
#include <mbgl/style/parser.hpp>
#include <mbgl/style/rapidjson_conversion.hpp>
#include <mbgl/style/conversion.hpp>
#include <mbgl/tile/geometry_tile_data.hpp>
#include <rapidjson/document.h>
@@ -16,7 +17,7 @@ typedef std::multimap<std::string, Value> Properties;
style::Filter parse(const char* expression) {
rapidjson::GenericDocument<rapidjson::UTF8<>, rapidjson::CrtAllocator> doc;
doc.Parse<0>(expression);
return style::parseFilter(doc);
return style::conversion::convertFilter(doc).get<style::Filter>();
}
static void Parse_Filter(benchmark::State& state) {

View File

@@ -2,7 +2,7 @@
#include <mbgl/style/types.hpp>
#include <mbgl/style/property_value.hpp>
#include <mbgl/style/transition_options.hpp>
#include <mbgl/style/filter.hpp>
#include <mbgl/util/variant.hpp>
#include <mbgl/util/optional.hpp>
@@ -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 <class V>
Result<Filter> 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<bool>`, absence indicating `v` is not a JSON boolean
* `toNumber(v)` -- returns `optional<float>`, absence indicating `v` is not a JSON number
* `toString(v)` -- returns `optional<std::string>`, absence indicating `v` is not a JSON string
* `toValue(v)` -- returns `optional<mbgl::Value>`, 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<PropertyValue<T>> convertPropertyValue(const V& value) {
return Function<T>(stops, *base);
}
Result<Value> normalizeFilterValue(const std::string& key, const optional<Value>&);
template <class V>
Result<Filter> convertFilter(const V& value);
template <class FilterType, class V>
Result<Filter> parseUnaryFilter(const V& value) {
if (arrayLength(value) < 2) {
return Error { "filter expression must have 2 elements" };
}
optional<std::string> key = toString(arrayMember(value, 1));
if (!key) {
return Error { "filter expression key must be a string" };
}
return FilterType { *key };
}
template <class FilterType, class V>
Result<Filter> parseBinaryFilter(const V& value) {
if (arrayLength(value) < 3) {
return Error { "filter expression must have 3 elements" };
}
optional<std::string> key = toString(arrayMember(value, 1));
if (!key) {
return Error { "filter expression key must be a string" };
}
Result<Value> filterValue = normalizeFilterValue(*key, toValue(arrayMember(value, 2)));
if (filterValue.is<Error>()) {
return filterValue.get<Error>();
}
return FilterType { *key, filterValue.get<Value>() };
}
template <class FilterType, class V>
Result<Filter> parseSetFilter(const V& value) {
if (arrayLength(value) < 2) {
return Error { "filter expression must at least 2 elements" };
}
optional<std::string> key = toString(arrayMember(value, 1));
if (!key) {
return Error { "filter expression key must be a string" };
}
std::vector<Value> values;
for (std::size_t i = 2; i < arrayLength(value); ++i) {
Result<Value> filterValue = normalizeFilterValue(*key, toValue(arrayMember(value, i)));
if (filterValue.is<Error>()) {
return filterValue.get<Error>();
}
values.push_back(filterValue.get<Value>());
}
return FilterType { *key, std::move(values) };
}
template <class FilterType, class V>
Result<Filter> parseCompoundFilter(const V& value) {
std::vector<Filter> filters;
for (std::size_t i = 1; i < arrayLength(value); ++i) {
Result<Filter> element = convertFilter(arrayMember(value, i));
if (element.is<Error>()) {
return element;
}
filters.push_back(element.get<Filter>());
}
return FilterType { std::move(filters) };
}
template <class V>
Result<Filter> 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<std::string> op = toString(arrayMember(value, 0));
if (!op) {
return Error { "filter operator must be a string" };
}
if (*op == "==") {
return parseBinaryFilter<EqualsFilter>(value);
} else if (*op == "!=") {
return parseBinaryFilter<NotEqualsFilter>(value);
} else if (*op == ">") {
return parseBinaryFilter<GreaterThanFilter>(value);
} else if (*op == ">=") {
return parseBinaryFilter<GreaterThanEqualsFilter>(value);
} else if (*op == "<") {
return parseBinaryFilter<LessThanFilter>(value);
} else if (*op == "<=") {
return parseBinaryFilter<LessThanEqualsFilter>(value);
} else if (*op == "in") {
return parseSetFilter<InFilter>(value);
} else if (*op == "!in") {
return parseSetFilter<NotInFilter>(value);
} else if (*op == "all") {
return parseCompoundFilter<AllFilter>(value);
} else if (*op == "any") {
return parseCompoundFilter<AnyFilter>(value);
} else if (*op == "none") {
return parseCompoundFilter<NoneFilter>(value);
} else if (*op == "has") {
return parseUnaryFilter<HasFilter>(value);
} else if (*op == "!has") {
return parseUnaryFilter<NotHasFilter>(value);
}
return Error { "filter operator must be one of \"==\", \"!=\", \">\", \">=\", \"<\", \"<=\", \"in\", \"!in\", \"all\", \"any\", \"none\", \"has\", or \"!has\"" };
}
} // namespace conversion
} // namespace style
} // namespace mbgl

View File

@@ -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"

View File

@@ -7,6 +7,7 @@
#pragma GCC diagnostic pop
#include <mbgl/util/optional.hpp>
#include <mbgl/util/feature.hpp>
namespace mbgl {
namespace style {
@@ -68,6 +69,24 @@ inline optional<std::string> toString(v8::Local<v8::Value> value) {
return std::string(*Nan::Utf8String(value));
}
inline optional<Value> toValue(v8::Local<v8::Value> 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

View File

@@ -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<NodeMap>(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<mbgl::style::Filter> converted
= mbgl::style::conversion::convertFilter(info[1]);
if (converted.is<mbgl::style::conversion::Error>()) {
Nan::ThrowTypeError(converted.get<mbgl::style::conversion::Error>().message);
return;
}
filter = std::move(converted.get<mbgl::style::Filter>());
}
if (!setFilter(*layer, filter)) {
return;
}
info.GetReturnValue().SetUndefined();
}
NAN_METHOD(NodeMap::DumpDebugLogs) {
auto nodeMap = Nan::ObjectWrap::Unwrap<NodeMap>(info.Holder());
if (!nodeMap->map) return Nan::ThrowError(releasedMessage());

View File

@@ -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);

View File

@@ -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<FillLayer>()) {
layer.as<FillLayer>()->setFilter(filter);
return true;
}
if (layer.is<LineLayer>()) {
layer.as<LineLayer>()->setFilter(filter);
return true;
}
if (layer.is<SymbolLayer>()) {
layer.as<SymbolLayer>()->setFilter(filter);
return true;
}
if (layer.is<CircleLayer>()) {
layer.as<CircleLayer>()->setFilter(filter);
return true;
}
Nan::ThrowTypeError("layer doesn't support filters");
return false;
}
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,25 @@
#include <mbgl/style/conversion.hpp>
namespace mbgl {
namespace style {
namespace conversion {
Result<Value> normalizeFilterValue(const std::string& key, const optional<Value>& 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

View File

@@ -9,6 +9,8 @@
#include <mbgl/style/layers/symbol_layer.hpp>
#include <mbgl/style/layers/raster_layer.hpp>
#include <mbgl/style/layers/background_layer.hpp>
#include <mbgl/style/rapidjson_conversion.hpp>
#include <mbgl/style/conversion.hpp>
#include <mbgl/platform/log.hpp>
@@ -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> filter = conversion::convertFilter(value["filter"]);
if (filter.is<Filter>()) {
impl->filter = filter.get<Filter>();
} else {
Log::Warning(Event::ParseStyle, filter.get<conversion::Error>().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 <class Expression>
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 <class Expression>
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 <class Expression>
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 <class Expression>
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<EqualsFilter>(value);
} else if (op == "!=") {
return parseBinaryFilter<NotEqualsFilter>(value);
} else if (op == ">") {
return parseBinaryFilter<GreaterThanFilter>(value);
} else if (op == ">=") {
return parseBinaryFilter<GreaterThanEqualsFilter>(value);
} else if (op == "<") {
return parseBinaryFilter<LessThanFilter>(value);
} else if (op == "<=") {
return parseBinaryFilter<LessThanEqualsFilter>(value);
} else if (op == "in") {
return parseSetFilter<InFilter>(value);
} else if (op == "!in") {
return parseSetFilter<NotInFilter>(value);
} else if (op == "all") {
return parseCompoundFilter<AllFilter>(value);
} else if (op == "any") {
return parseCompoundFilter<AnyFilter>(value);
} else if (op == "none") {
return parseCompoundFilter<NoneFilter>(value);
} else if (op == "has") {
return parseUnaryFilter<HasFilter>(value);
} else if (op == "!has") {
return parseUnaryFilter<NotHasFilter>(value);
} else {
Log::Warning(Event::ParseStyle, "filter operator must be one of \"==\", \"!=\", \">\", \">=\", \"<\", \"<=\", \"in\", \"!in\", \"all\", \"any\", \"none\", \"has\", or \"!has\"");
return empty;
}
}
std::vector<FontStack> Parser::fontStacks() const {
std::set<FontStack> result;

View File

@@ -2,7 +2,6 @@
#include <mbgl/style/layer.hpp>
#include <mbgl/style/source.hpp>
#include <mbgl/style/filter.hpp>
#include <mbgl/util/rapidjson.hpp>
#include <mbgl/util/font_stack.hpp>
@@ -16,8 +15,6 @@
namespace mbgl {
namespace style {
Filter parseFilter(const JSValue&);
class Parser {
public:
~Parser();

View File

@@ -1,6 +1,7 @@
#pragma once
#include <mbgl/util/rapidjson.hpp>
#include <mbgl/util/feature.hpp>
namespace mbgl {
namespace style {
@@ -50,6 +51,28 @@ inline optional<std::string> toString(const JSValue& value) {
return {{ value.GetString(), value.GetStringLength() }};
}
inline optional<Value> 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

View File

@@ -2,7 +2,8 @@
#include <mbgl/style/filter.hpp>
#include <mbgl/style/filter_evaluator.hpp>
#include <mbgl/style/parser.hpp>
#include <mbgl/style/rapidjson_conversion.hpp>
#include <mbgl/style/conversion.hpp>
#include <rapidjson/document.h>
@@ -16,7 +17,7 @@ typedef std::multimap<std::string, mbgl::Value> Properties;
Filter parse(const char * expression) {
rapidjson::GenericDocument<rapidjson::UTF8<>, rapidjson::CrtAllocator> doc;
doc.Parse<0>(expression);
return parseFilter(doc);
return conversion::convertFilter(doc).get<Filter>();
}
bool evaluate(const Filter& filter, const Properties& properties, FeatureType type = FeatureType::Unknown) {