From 25c5c3bbb527509b4b840005510cbb0d85208b40 Mon Sep 17 00:00:00 2001 From: Anand Thakker Date: Thu, 6 Jul 2017 09:37:59 -0400 Subject: Expose Expression in node and hook up integration test --- cmake/node.cmake | 2 + package.json | 2 +- platform/node/src/node_expression.cpp | 126 ++++++++++++++++++++++++++++ platform/node/src/node_expression.hpp | 36 ++++++++ platform/node/src/node_mapbox_gl_native.cpp | 2 + platform/node/test/expression.test.js | 52 ++++++++++++ 6 files changed, 219 insertions(+), 1 deletion(-) create mode 100644 platform/node/src/node_expression.cpp create mode 100644 platform/node/src/node_expression.hpp create mode 100644 platform/node/test/expression.test.js diff --git a/cmake/node.cmake b/cmake/node.cmake index 502edd8293..add66e7fbb 100644 --- a/cmake/node.cmake +++ b/cmake/node.cmake @@ -21,6 +21,8 @@ target_sources(mbgl-node PRIVATE platform/node/src/node_feature.cpp PRIVATE platform/node/src/node_thread_pool.hpp PRIVATE platform/node/src/node_thread_pool.cpp + PRIVATE platform/node/src/node_expression.hpp + PRIVATE platform/node/src/node_expression.cpp PRIVATE platform/node/src/util/async_queue.hpp ) diff --git a/package.json b/package.json index e428a0db43..cdf09636c7 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "license": "BSD-2-Clause", "dependencies": { "node-pre-gyp": "^0.6.28", - "nan": "^2.4.0" + "nan": "^2.6.2" }, "devDependencies": { "aws-sdk": "^2.3.5", diff --git a/platform/node/src/node_expression.cpp b/platform/node/src/node_expression.cpp new file mode 100644 index 0000000000..51a9fca5ef --- /dev/null +++ b/platform/node/src/node_expression.cpp @@ -0,0 +1,126 @@ +#include "node_expression.hpp" +#include "node_conversion.hpp" + +#include +#include +#include +#include + +using namespace mbgl::style; +using namespace mbgl::style::expression; + +namespace node_mbgl { + +Nan::Persistent NodeExpression::constructor; + +void NodeExpression::Init(v8::Local target) { + v8::Local tpl = Nan::New(New); + tpl->SetClassName(Nan::New("Expression").ToLocalChecked()); + tpl->InstanceTemplate()->SetInternalFieldCount(1); // what is this doing? + + Nan::SetPrototypeMethod(tpl, "evaluate", Evaluate); + Nan::SetPrototypeMethod(tpl, "getType", GetType); + Nan::SetPrototypeMethod(tpl, "isFeatureConstant", IsFeatureConstant); + Nan::SetPrototypeMethod(tpl, "isZoomConstant", IsZoomConstant); + + constructor.Reset(tpl->GetFunction()); // what is this doing? + Nan::Set(target, Nan::New("Expression").ToLocalChecked(), tpl->GetFunction()); +} + +void NodeExpression::New(const Nan::FunctionCallbackInfo& info) { + if (!info.IsConstructCall()) { + return Nan::ThrowTypeError("Use the new operator to create new Expression objects"); + } + + if (info.Length() < 1 || info[0]->IsUndefined()) { + return Nan::ThrowTypeError("Requires a JSON style expression argument."); + } + + auto expr = info[0]; + + try { + conversion::Error error; + auto expression = conversion::convert>(expr, error); + if (!expression) { + return Nan::ThrowTypeError(error.message.c_str()); + } + auto nodeExpr = new NodeExpression(std::move(*expression)); + nodeExpr->Wrap(info.This()); + } catch(std::exception &ex) { + return Nan::ThrowError(ex.what()); + } + + info.GetReturnValue().Set(info.This()); +} + +void NodeExpression::Evaluate(const Nan::FunctionCallbackInfo& info) { + NodeExpression* nodeExpr = ObjectWrap::Unwrap(info.Holder()); + + if (info.Length() < 2) { + return Nan::ThrowTypeError("Requires arguments zoom and feature arguments."); + } + + float zoom = info[0]->NumberValue(); + + // Pending https://github.com/mapbox/mapbox-gl-native/issues/5623, + // stringify the geojson feature in order to use convert() + Nan::JSON NanJSON; + Nan::MaybeLocal geojsonString = NanJSON.Stringify(Nan::To(info[1]).ToLocalChecked()); + if (geojsonString.IsEmpty()) { + return Nan::ThrowTypeError("couldn't stringify JSON"); + } + conversion::Error conversionError; + mbgl::optional geoJSON = conversion::convert(*Nan::Utf8String(geojsonString.ToLocalChecked()), conversionError); + if (!geoJSON) { + Nan::ThrowTypeError(conversionError.message.c_str()); + return; + } + + try { + mapbox::geojson::feature feature = geoJSON->get(); + Error error; + auto result = nodeExpr->expression->evaluate(zoom, feature, error); + if (result) { + result->match( + [&] (const std::array&) {}, + [&] (const std::array&) {}, + [&] (const std::string s) { + info.GetReturnValue().Set(Nan::New(s.c_str()).ToLocalChecked()); + }, + [&] (const mbgl::Color& c) { + info.GetReturnValue().Set(Nan::New(c.stringify().c_str()).ToLocalChecked()); + }, + [&] (const auto& v) { + info.GetReturnValue().Set(Nan::New(v)); + } + ); + } else { + v8::Local res = Nan::New(); + Nan::Set(res, + Nan::New("error").ToLocalChecked(), + Nan::New(error.message.c_str()).ToLocalChecked()); + info.GetReturnValue().Set(res); + } + } catch(std::exception &ex) { + return Nan::ThrowTypeError(ex.what()); + } +} + +void NodeExpression::GetType(const Nan::FunctionCallbackInfo& info) { + NodeExpression* nodeExpr = ObjectWrap::Unwrap(info.Holder()); + const auto& type = nodeExpr->expression->getType(); + const auto& name = type.match([&] (const auto& t) { return t.getName(); }); + info.GetReturnValue().Set(Nan::New(name.c_str()).ToLocalChecked()); +} + +void NodeExpression::IsFeatureConstant(const Nan::FunctionCallbackInfo& info) { + NodeExpression* nodeExpr = ObjectWrap::Unwrap(info.Holder()); + info.GetReturnValue().Set(Nan::New(nodeExpr->expression->isFeatureConstant())); +} + +void NodeExpression::IsZoomConstant(const Nan::FunctionCallbackInfo& info) { + NodeExpression* nodeExpr = ObjectWrap::Unwrap(info.Holder()); + info.GetReturnValue().Set(Nan::New(nodeExpr->expression->isZoomConstant())); +} + +} // namespace node_mbgl diff --git a/platform/node/src/node_expression.hpp b/platform/node/src/node_expression.hpp new file mode 100644 index 0000000000..8913bead1b --- /dev/null +++ b/platform/node/src/node_expression.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include +#include +#include + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-parameter" +#pragma GCC diagnostic ignored "-Wshadow" +#include +#pragma GCC diagnostic pop + +using namespace mbgl::style::expression; + +namespace node_mbgl { + +class NodeExpression : public Nan::ObjectWrap { +public: + static void Init(v8::Local); + +private: + NodeExpression(std::unique_ptr expression_) : + expression(std::move(expression_)) + {}; + + static void New(const Nan::FunctionCallbackInfo&); + static void Evaluate(const Nan::FunctionCallbackInfo&); + static void GetType(const Nan::FunctionCallbackInfo&); + static void IsFeatureConstant(const Nan::FunctionCallbackInfo&); + static void IsZoomConstant(const Nan::FunctionCallbackInfo&); + static Nan::Persistent constructor; + + std::unique_ptr expression; +}; + +} // namespace node_mbgl diff --git a/platform/node/src/node_mapbox_gl_native.cpp b/platform/node/src/node_mapbox_gl_native.cpp index cdcc982220..96e96e4298 100644 --- a/platform/node/src/node_mapbox_gl_native.cpp +++ b/platform/node/src/node_mapbox_gl_native.cpp @@ -10,6 +10,7 @@ #include "node_map.hpp" #include "node_logging.hpp" #include "node_request.hpp" +#include "node_expression.hpp" void RegisterModule(v8::Local target, v8::Local module) { // This has the effect of: @@ -20,6 +21,7 @@ void RegisterModule(v8::Local target, v8::Local module) node_mbgl::NodeMap::Init(target); node_mbgl::NodeRequest::Init(); + node_mbgl::NodeExpression::Init(target); // Exports Resource constants. v8::Local resource = Nan::New(); diff --git a/platform/node/test/expression.test.js b/platform/node/test/expression.test.js new file mode 100644 index 0000000000..c243ba1e0e --- /dev/null +++ b/platform/node/test/expression.test.js @@ -0,0 +1,52 @@ +'use strict'; + +var suite = require('../../../mapbox-gl-js/test/integration').expression; +var mbgl = require('../index'); + +var tests; + +if (process.argv[1] === __filename && process.argv.length > 2) { + tests = process.argv.slice(2); +} + +suite.run('native', {tests: tests}, (fixture) => { + const compileResult = {}; + const testResult = { + compileResult + }; + + try { + const expression = new mbgl.Expression(fixture.expression); + compileResult.result = 'success'; + compileResult.isFeatureConstant = expression.isFeatureConstant(); + compileResult.isZoomConstant = expression.isZoomConstant(); + compileResult.type = expression.getType(); + + if (fixture.evaluate) { + const evaluateResults = []; + for (const input of fixture.evaluate) { + const zoom = typeof input[0].zoom === 'number' ? + input[0].zoom : -1; + + const feature = Object.assign({ + type: 'Feature', + properties: {}, + geometry: { type: 'Unknown', coordinates: [] } + }, input[1]) + + const output = expression.evaluate(zoom, feature); + evaluateResults.push(output); + } + + if (evaluateResults.length) { + testResult.evaluateResults = evaluateResults; + } + } + } catch (e) { + compileResult.result = 'error'; + compileResult.errors = [e.message]; + } + + return testResult; +}); + -- cgit v1.2.1