Skip to content

Commit d55e480

Browse files
committed
N-API mapnik.Expression + tests (WIP) [skip ci]
1 parent c26360e commit d55e480

6 files changed

+103
-116
lines changed

binding.gyp

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
"src/mapnik_layer.cpp",
4343
"src/mapnik_datasource.cpp",
4444
"src/mapnik_featureset.cpp",
45-
#"src/mapnik_expression.cpp",
45+
"src/mapnik_expression.cpp",
4646
#"src/mapnik_cairo_surface.cpp",
4747
#"src/mapnik_vector_tile.cpp"
4848
],

src/mapnik_expression.cpp

+42-57
Original file line numberDiff line numberDiff line change
@@ -1,120 +1,105 @@
11
#include "utils.hpp"
22
#include "mapnik_expression.hpp"
33
#include "mapnik_feature.hpp"
4-
#include "utils.hpp"
54
#include "object_to_container.hpp"
65

76
// mapnik
8-
#include <mapnik/version.hpp>
97
#include <mapnik/attribute.hpp>
108
#include <mapnik/expression_string.hpp>
119
#include <mapnik/expression_evaluator.hpp>
1210

13-
// stl
14-
#include <exception> // for exception
1511

1612
Napi::FunctionReference Expression::constructor;
1713

18-
void Expression::Initialize(Napi::Object target) {
19-
20-
Napi::HandleScope scope(env);
21-
22-
Napi::FunctionReference lcons = Napi::Function::New(env, Expression::New);
23-
24-
lcons->SetClassName(Napi::String::New(env, "Expression"));
25-
26-
InstanceMethod("toString", &toString),
27-
InstanceMethod("evaluate", &evaluate),
28-
29-
(target).Set(Napi::String::New(env, "Expression"), Napi::GetFunction(lcons));
30-
constructor.Reset(lcons);
14+
Napi::Object Expression::Initialize(Napi::Env env, Napi::Object exports)
15+
{
16+
Napi::Function func = DefineClass(env, "Expression", {
17+
InstanceMethod<&Expression::evaluate>("evaluate"),
18+
InstanceMethod<&Expression::toString>("toString")
19+
});
20+
constructor = Napi::Persistent(func);
21+
constructor.SuppressDestruct();
22+
exports.Set("Expression", func);
23+
return exports;
3124
}
3225

33-
Expression::Expression() : Napi::ObjectWrap<Expression>(),
34-
this_() {}
3526

36-
Expression::~Expression()
27+
Expression::Expression(Napi::CallbackInfo const& info)
28+
: Napi::ObjectWrap<Expression>(info)
3729
{
38-
}
30+
Napi::Env env = info.Env();
3931

40-
Napi::Value Expression::New(Napi::CallbackInfo const& info)
41-
{
42-
if (!info.IsConstructCall())
32+
if (info.Length() != 1 || !info[0].IsString())
4333
{
44-
Napi::Error::New(env, "Cannot call constructor as function, you need to use 'new' keyword").ThrowAsJavaScriptException();
45-
return env.Null();
34+
Napi::TypeError::New(env, "invalid arguments: accepts a single argument of string type").ThrowAsJavaScriptException();
35+
return;
4636
}
47-
48-
mapnik::expression_ptr e_ptr;
4937
try
5038
{
51-
if (info.Length() == 1 && info[0].IsString()) {
52-
e_ptr = mapnik::parse_expression(TOSTR(info[0]));
53-
} else {
54-
Napi::TypeError::New(env, "invalid arguments: accepts a single argument of string type").ThrowAsJavaScriptException();
55-
return env.Null();
56-
}
39+
expression_ = mapnik::parse_expression(info[0].As<Napi::String>());
5740
}
5841
catch (std::exception const& ex)
5942
{
6043
Napi::Error::New(env, ex.what()).ThrowAsJavaScriptException();
61-
return env.Null();
6244
}
63-
64-
Expression* e = new Expression();
65-
e->Wrap(info.This());
66-
e->this_ = e_ptr;
67-
return info.This();
6845
}
6946

7047
Napi::Value Expression::toString(Napi::CallbackInfo const& info)
7148
{
72-
Expression* e = info.Holder().Unwrap<Expression>();
73-
return Napi::New(env, mapnik::to_expression_string(*e->get()));
49+
Napi::Env env = info.Env();
50+
Napi::EscapableHandleScope scope(env);
51+
52+
Napi::String str = Napi::String::New(env, mapnik::to_expression_string(*expression_));
53+
return scope.Escape(str);
7454
}
7555

7656
Napi::Value Expression::evaluate(Napi::CallbackInfo const& info)
7757
{
78-
if (info.Length() < 1) {
58+
Napi::Env env = info.Env();
59+
Napi::EscapableHandleScope scope(env);
60+
61+
if (info.Length() < 1)
62+
{
7963
Napi::Error::New(env, "requires a mapnik.Feature as an argument").ThrowAsJavaScriptException();
80-
return env.Null();
64+
return env.Undefined();
8165
}
8266

8367
if (!info[0].IsObject())
8468
{
8569
Napi::TypeError::New(env, "first argument is invalid, must be a mapnik.Feature").ThrowAsJavaScriptException();
86-
return env.Null();
70+
return env.Undefined();
8771
}
88-
89-
if (!Napi::New(env, Feature::constructor)->HasInstance(info[0])) {
72+
Napi::Object obj = info[0].As<Napi::Object>();
73+
if (!obj.InstanceOf(Feature::constructor.Value()))
74+
{
9075
Napi::TypeError::New(env, "first argument is invalid, must be a mapnik.Feature").ThrowAsJavaScriptException();
91-
return env.Null();
76+
return env.Undefined();
9277
}
9378

94-
Feature* f = info[0].As<Napi::Object>().Unwrap<Feature>();
79+
Feature *f = Napi::ObjectWrap<Feature>::Unwrap(obj);
9580

96-
Expression* e = info.Holder().Unwrap<Expression>();
9781
mapnik::attributes vars;
9882
if (info.Length() > 1)
9983
{
10084
if (!info[1].IsObject())
10185
{
10286
Napi::TypeError::New(env, "optional second argument must be an options object").ThrowAsJavaScriptException();
103-
return env.Null();
87+
return env.Undefined();
10488
}
10589
Napi::Object options = info[1].As<Napi::Object>();
10690

107-
if ((options).Has(Napi::String::New(env, "variables")).FromMaybe(false))
91+
if (options.Has("variables"))
10892
{
109-
Napi::Value bind_opt = (options).Get(Napi::String::New(env, "variables"));
93+
Napi::Value bind_opt = options.Get("variables");
11094
if (!bind_opt.IsObject())
11195
{
11296
Napi::TypeError::New(env, "optional arg 'variables' must be an object").ThrowAsJavaScriptException();
113-
return env.Null();
97+
return env.Undefined();
11498
}
115-
object_to_container(vars,bind_opt->ToObject(Napi::GetCurrentContext()));
99+
object_to_container(vars, bind_opt.As<Napi::Object>());
116100
}
117101
}
118-
mapnik::value value_obj = mapnik::util::apply_visitor(mapnik::evaluate<mapnik::feature_impl,mapnik::value,mapnik::attributes>(*(f->get()),vars),*(e->get()));
119-
return mapnik::util::apply_visitor(node_mapnik::value_converter(),value_obj);
102+
using namespace mapnik;
103+
value val =util::apply_visitor(mapnik::evaluate<feature_impl, value, attributes>(*f->impl(), vars),*expression_);
104+
return scope.Escape(util::apply_visitor(node_mapnik::value_converter(env), val));
120105
}

src/mapnik_expression.hpp

+8-11
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,14 @@
44
// mapnik
55
#include <mapnik/expression.hpp>
66

7-
class Expression : public Napi::ObjectWrap<Expression> {
7+
class Expression : public Napi::ObjectWrap<Expression>
8+
{
89
public:
9-
static Napi::FunctionReference constructor;
10-
static void Initialize(Napi::Object target);
11-
static Napi::Value New(Napi::CallbackInfo const& info);
12-
static Napi::Value toString(Napi::CallbackInfo const& info);
13-
static Napi::Value evaluate(Napi::CallbackInfo const& info);
14-
15-
Expression();
16-
inline mapnik::expression_ptr get() { return this_; }
17-
10+
static Napi::Object Initialize(Napi::Env env, Napi::Object exports);
11+
explicit Expression(Napi::CallbackInfo const& info);
12+
Napi::Value toString(Napi::CallbackInfo const& info);
13+
Napi::Value evaluate(Napi::CallbackInfo const& info);
1814
private:
19-
mapnik::expression_ptr this_;
15+
static Napi::FunctionReference constructor;
16+
mapnik::expression_ptr expression_;
2017
};

src/mapnik_feature.hpp

+2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
class Feature : public Napi::ObjectWrap<Feature>
1212
{
1313
friend class Featureset;
14+
friend class Expression;
1415
public:
1516
// initialiser
1617
static Napi::Object Initialize(Napi::Env env, Napi::Object exports);
@@ -23,6 +24,7 @@ class Feature : public Napi::ObjectWrap<Feature>
2324
Napi::Value attributes(Napi::CallbackInfo const& info);
2425
Napi::Value geometry(Napi::CallbackInfo const& info);
2526
Napi::Value toJSON(Napi::CallbackInfo const& info);
27+
inline mapnik::feature_ptr impl() const {return feature_;}
2628
private:
2729
static Napi::FunctionReference constructor;
2830
mapnik::feature_ptr feature_;

src/node_mapnik.cpp

+2-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
//#include "mapnik_grid.hpp"
2424
//#include "mapnik_grid_view.hpp"
2525
#endif
26-
//#include "mapnik_expression.hpp"
26+
#include "mapnik_expression.hpp"
2727
//#include "utils.hpp"
2828
#include "blend.hpp"
2929

@@ -319,6 +319,7 @@ Napi::Object init(Napi::Env env, Napi::Object exports)
319319
Featureset::Initialize(env, exports);
320320
Layer::Initialize(env, exports);
321321
Map::Initialize(env, exports);
322+
Expression::Initialize(env, exports);
322323
// enums
323324
init_image_types(env, exports);
324325
init_image_scalings(env, exports);

test/expression.test.js

+48-46
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,53 @@
11
"use strict";
22

3+
var test = require('tape');
34
var mapnik = require('../');
4-
var assert = require('assert');
5-
6-
describe('mapnik.Expression', function() {
7-
it('should throw with invalid usage', function() {
8-
// no 'new' keyword
9-
assert.throws(function() { mapnik.Expression(); });
10-
// invalid args
11-
assert.throws(function() { new mapnik.Expression(); });
12-
assert.throws(function() { new mapnik.Expression(1); });
13-
assert.throws(function() { new mapnik.Expression('[asdfadsa]]'); });
14-
});
15-
16-
it('should accept complex expressions', function() {
17-
// valid expression strings
18-
var expr = new mapnik.Expression('[ATTR]');
19-
expr = new mapnik.Expression('[ATTR]+2');
20-
expr = new mapnik.Expression('[ATTR]/2');
21-
22-
expr = new mapnik.Expression('[ATTR1]/[ATTR2]');
23-
assert.equal(expr.toString(), '[ATTR1]/[ATTR2]');
24-
25-
expr = new mapnik.Expression('\'literal\'');
26-
assert.equal(expr.toString(), "'literal'");
27-
});
28-
29-
it('should support evaluation to js types', function() {
30-
var expr = new mapnik.Expression("[attr]='value'");
31-
var feature = new mapnik.Feature.fromJSON('{"type":"Feature","properties":{"attr":"value"},"geometry":null}');
32-
33-
// Test bad parameters
34-
assert.throws(function() { expr.evaluate(); });
35-
assert.throws(function() { expr.evaluate(null); });
36-
assert.throws(function() { expr.evaluate(feature, null); });
37-
assert.throws(function() { expr.evaluate(feature, {variables:null}); });
38-
39-
assert.equal(expr.evaluate(feature), true);
40-
assert.equal(expr.evaluate(feature).toString(), 'true');
41-
});
42-
43-
it('should support evaluation with variables', function() {
44-
var expr = new mapnik.Expression("[integer]=@integer and [bool]=@bool and [string]=@string and [double]=@double");
45-
var options = {variables: { 'integer': 22, 'bool': true, 'string': "string", 'double': 1.0001 } };
46-
var feature = new mapnik.Feature.fromJSON('{"type":"Feature","properties":{"integer":22, "bool": 1, "string": "string", "double":1.0001},"geometry":null}');
47-
assert.equal(expr.evaluate(feature, options), true);
48-
assert.equal(expr.evaluate(feature, options).toString(), 'true');
49-
});
5+
6+
7+
test('should throw with invalid usage', (assert) => {
8+
// no 'new' keyword
9+
assert.throws(function() { mapnik.Expression(); });
10+
// invalid args
11+
assert.throws(function() { new mapnik.Expression(); });
12+
assert.throws(function() { new mapnik.Expression(1); });
13+
assert.throws(function() { new mapnik.Expression('[asdfadsa]]'); });
14+
assert.end();
15+
});
16+
17+
test('should accept complex expressions', (assert) => {
18+
// valid expression strings
19+
var expr = new mapnik.Expression('[ATTR]');
20+
expr = new mapnik.Expression('[ATTR]+2');
21+
expr = new mapnik.Expression('[ATTR]/2');
22+
23+
expr = new mapnik.Expression('[ATTR1]/[ATTR2]');
24+
assert.equal(expr.toString(), '[ATTR1]/[ATTR2]');
25+
26+
expr = new mapnik.Expression('\'literal\'');
27+
assert.equal(expr.toString(), "'literal'");
28+
assert.end();
5029
});
5130

31+
test('should support evaluation to js types', (assert) => {
32+
var expr = new mapnik.Expression("[attr]='value'");
33+
var feature = new mapnik.Feature.fromJSON('{"type":"Feature","properties":{"attr":"value"},"geometry":null}');
34+
35+
// Test bad parameters
36+
assert.throws(function() { expr.evaluate(); });
37+
assert.throws(function() { expr.evaluate(null); });
38+
assert.throws(function() { expr.evaluate(feature, null); });
39+
assert.throws(function() { expr.evaluate(feature, {variables:null}); });
40+
41+
assert.equal(expr.evaluate(feature), true);
42+
assert.equal(expr.evaluate(feature).toString(), 'true');
43+
assert.end();
44+
});
45+
46+
test('should support evaluation with variables', (assert) => {
47+
var expr = new mapnik.Expression("[integer]=@integer and [bool]=@bool and [string]=@string and [double]=@double");
48+
var options = {variables: { 'integer': 22, 'bool': true, 'string': "string", 'double': 1.0001 } };
49+
var feature = new mapnik.Feature.fromJSON('{"type":"Feature","properties":{"integer":22, "bool": 1, "string": "string", "double":1.0001},"geometry":null}');
50+
assert.equal(expr.evaluate(feature, options), true);
51+
assert.equal(expr.evaluate(feature, options).toString(), 'true');
52+
assert.end();
53+
});

0 commit comments

Comments
 (0)