Skip to content
Permalink
699a2724d6
Go to file
 
 
Cannot retrieve contributors at this time
605 lines (527 sloc) 17.9 KB
#if defined(GRID_RENDERER)
// mapnik
#include <mapnik/version.hpp>
#include "mapnik_grid.hpp"
#include "mapnik_grid_view.hpp"
#include "js_grid_utils.hpp"
#include "utils.hpp"
// std
#include <exception>
Nan::Persistent<v8::FunctionTemplate> Grid::constructor;
/**
* **`mapnik.Grid`**
*
* Generator for [UTFGrid](https://www.mapbox.com/guides/an-open-platform)
* representations of data.
*
* @class Grid
* @param {number} width
* @param {number} height
* @param {Object} [options={}] optional argument, which can have a 'key' property
* @property {string} key
*/
void Grid::Initialize(v8::Local<v8::Object> target) {
Nan::HandleScope scope;
v8::Local<v8::FunctionTemplate> lcons = Nan::New<v8::FunctionTemplate>(Grid::New);
lcons->InstanceTemplate()->SetInternalFieldCount(1);
lcons->SetClassName(Nan::New("Grid").ToLocalChecked());
// methods
Nan::SetPrototypeMethod(lcons, "encodeSync", encodeSync);
Nan::SetPrototypeMethod(lcons, "encode", encode);
Nan::SetPrototypeMethod(lcons, "addField", addField);
Nan::SetPrototypeMethod(lcons, "fields", fields);
Nan::SetPrototypeMethod(lcons, "view", view);
Nan::SetPrototypeMethod(lcons, "width", width);
Nan::SetPrototypeMethod(lcons, "height", height);
Nan::SetPrototypeMethod(lcons, "painted", painted);
Nan::SetPrototypeMethod(lcons, "clear", clear);
Nan::SetPrototypeMethod(lcons, "clearSync", clearSync);
// properties
ATTR(lcons, "key", get_key, set_key);
target->Set(Nan::New("Grid").ToLocalChecked(), lcons->GetFunction());
NODE_MAPNIK_DEFINE_64_BIT_CONSTANT(lcons->GetFunction(), "base_mask", mapnik::grid::base_mask);
constructor.Reset(lcons);
}
Grid::Grid(unsigned int width, unsigned int height, std::string const& key) :
Nan::ObjectWrap(),
this_(std::make_shared<mapnik::grid>(width,height,key))
{
}
Grid::~Grid()
{
}
NAN_METHOD(Grid::New)
{
if (!info.IsConstructCall())
{
Nan::ThrowError("Cannot call constructor as function, you need to use 'new' keyword");
return;
}
if (info.Length() >= 2)
{
if (!info[0]->IsNumber() || !info[1]->IsNumber())
{
Nan::ThrowTypeError("Grid 'width' and 'height' must be a integers");
return;
}
// defaults
std::string key("__id__");
if (info.Length() >= 3) {
if (!info[2]->IsObject())
{
Nan::ThrowTypeError("optional third arg must be an options object");
return;
}
v8::Local<v8::Object> options = info[2].As<v8::Object>();
if (options->Has(Nan::New("key").ToLocalChecked())) {
v8::Local<v8::Value> bind_opt = options->Get(Nan::New("key").ToLocalChecked());
if (!bind_opt->IsString())
{
Nan::ThrowTypeError("optional arg 'key' must be an string");
return;
}
key = TOSTR(bind_opt);
}
}
Grid* g = new Grid(info[0]->IntegerValue(), info[1]->IntegerValue(), key);
g->Wrap(info.This());
info.GetReturnValue().Set(info.This());
return;
}
else
{
Nan::ThrowError("please provide Grid width and height");
return;
}
}
NAN_METHOD(Grid::clearSync)
{
info.GetReturnValue().Set(_clearSync(info));
}
v8::Local<v8::Value> Grid::_clearSync(Nan::NAN_METHOD_ARGS_TYPE info)
{
Nan::EscapableHandleScope scope;
Grid* g = Nan::ObjectWrap::Unwrap<Grid>(info.Holder());
g->get()->clear();
return scope.Escape(Nan::Undefined());
}
typedef struct {
uv_work_t request;
Grid* g;
bool error;
std::string error_name;
Nan::Persistent<v8::Function> cb;
} clear_grid_baton_t;
NAN_METHOD(Grid::clear)
{
Grid* g = Nan::ObjectWrap::Unwrap<Grid>(info.Holder());
if (info.Length() == 0) {
info.GetReturnValue().Set(_clearSync(info));
return;
}
// ensure callback is a function
v8::Local<v8::Value> callback = info[info.Length()-1];
if (!info[info.Length()-1]->IsFunction())
{
Nan::ThrowTypeError("last argument must be a callback function");
return;
}
clear_grid_baton_t *closure = new clear_grid_baton_t();
closure->request.data = closure;
closure->g = g;
closure->error = false;
closure->cb.Reset(callback.As<v8::Function>());
uv_queue_work(uv_default_loop(), &closure->request, EIO_Clear, (uv_after_work_cb)EIO_AfterClear);
g->Ref();
return;
}
void Grid::EIO_Clear(uv_work_t* req)
{
clear_grid_baton_t *closure = static_cast<clear_grid_baton_t *>(req->data);
try
{
closure->g->get()->clear();
}
catch(std::exception const& ex)
{
// It is not clear how clear could EVER throw an exception but leaving
// it in but removing the following lines from coverage test.
/* LCOV_EXCL_START */
closure->error = true;
closure->error_name = ex.what();
/* LCOV_EXCL_STOP */
}
}
void Grid::EIO_AfterClear(uv_work_t* req)
{
Nan::HandleScope scope;
clear_grid_baton_t *closure = static_cast<clear_grid_baton_t *>(req->data);
if (closure->error)
{
// There seems to be no possible way for the exception to be thrown in the previous
// process and therefore not possible to have an error here so removing it from code
// coverage
/* LCOV_EXCL_START */
v8::Local<v8::Value> argv[1] = { Nan::Error(closure->error_name.c_str()) };
Nan::MakeCallback(Nan::GetCurrentContext()->Global(), Nan::New(closure->cb), 1, argv);
/* LCOV_EXCL_STOP */
}
else
{
v8::Local<v8::Value> argv[2] = { Nan::Null(), closure->g->handle() };
Nan::MakeCallback(Nan::GetCurrentContext()->Global(), Nan::New(closure->cb), 2, argv);
}
closure->g->Unref();
closure->cb.Reset();
delete closure;
}
NAN_METHOD(Grid::painted)
{
Grid* g = Nan::ObjectWrap::Unwrap<Grid>(info.Holder());
info.GetReturnValue().Set(Nan::New(g->get()->painted()));
}
/**
* Get this grid's width
* @memberof Grid
* @instance
* @name width
* @returns {number} width
*/
NAN_METHOD(Grid::width)
{
Grid* g = Nan::ObjectWrap::Unwrap<Grid>(info.Holder());
info.GetReturnValue().Set(Nan::New<v8::Integer>((unsigned)g->get()->width()));
}
/**
* Get this grid's height
* @memberof Grid
* @instance
* @name height
* @returns {number} height
*/
NAN_METHOD(Grid::height)
{
Grid* g = Nan::ObjectWrap::Unwrap<Grid>(info.Holder());
info.GetReturnValue().Set(Nan::New<v8::Integer>(static_cast<unsigned>(g->get()->height())));
}
NAN_GETTER(Grid::get_key)
{
Grid* g = Nan::ObjectWrap::Unwrap<Grid>(info.Holder());
info.GetReturnValue().Set(Nan::New<v8::String>(g->get()->get_key()).ToLocalChecked());
}
NAN_SETTER(Grid::set_key)
{
Grid* g = Nan::ObjectWrap::Unwrap<Grid>(info.Holder());
if (!value->IsString())
{
Nan::ThrowTypeError("key must be an string");
return;
}
g->get()->set_key(TOSTR(value));
}
/**
* Add a field to this grid's output
* @memberof Grid
* @instance
* @name addField
* @param {string} field
*/
NAN_METHOD(Grid::addField)
{
Grid* g = Nan::ObjectWrap::Unwrap<Grid>(info.Holder());
if (info.Length() == 1) {
if (!info[0]->IsString())
{
Nan::ThrowTypeError("argument must be a string");
return;
}
g->get()->add_field(TOSTR(info[0]));
return;
}
else
{
Nan::ThrowTypeError("one parameter, a string is required");
return;
}
}
/**
* Get all of this grid's fields
* @memberof Grid
* @instance
* @name addField
* @returns {v8::Array<string>} fields
*/
NAN_METHOD(Grid::fields)
{
Grid* g = Nan::ObjectWrap::Unwrap<Grid>(info.Holder());
std::set<std::string> const& a = g->get()->get_fields();
std::set<std::string>::const_iterator itr = a.begin();
std::set<std::string>::const_iterator end = a.end();
v8::Local<v8::Array> l = Nan::New<v8::Array>(a.size());
int idx = 0;
for (; itr != end; ++itr)
{
std::string name = *itr;
l->Set(idx, Nan::New<v8::String>(name).ToLocalChecked());
++idx;
}
info.GetReturnValue().Set(l);
}
/**
* Get a constrained view of this field given x, y, width, height parameters.
* @memberof Grid
* @instance
* @name view
* @param {number} x
* @param {number} y
* @param {number} width
* @param {number} height
* @returns {mapnik.Grid} a grid constrained to this new view
*/
NAN_METHOD(Grid::view)
{
if ( (info.Length() != 4) || (!info[0]->IsNumber() && !info[1]->IsNumber() && !info[2]->IsNumber() && !info[3]->IsNumber() ))
{
Nan::ThrowTypeError("requires 4 integer arguments: x, y, width, height");
return;
}
unsigned x = info[0]->IntegerValue();
unsigned y = info[1]->IntegerValue();
unsigned w = info[2]->IntegerValue();
unsigned h = info[3]->IntegerValue();
Grid* g = Nan::ObjectWrap::Unwrap<Grid>(info.Holder());
info.GetReturnValue().Set(GridView::NewInstance(g,x,y,w,h));
}
/**
* Get a constrained view of this field given x, y, width, height parameters.
* @memberof Grid
* @instance
* @name encodeSync
* @param {Object} [options={ resolution: 4, features: false }]
* @returns {Object} an encoded field with `grid`, `keys`, and `data` members.
*/
NAN_METHOD(Grid::encodeSync)
{
Grid* g = Nan::ObjectWrap::Unwrap<Grid>(info.Holder());
// defaults
unsigned int resolution = 4;
bool add_features = true;
// options hash
if (info.Length() >= 1) {
if (!info[0]->IsObject())
{
Nan::ThrowTypeError("optional arg must be an options object");
return;
}
v8::Local<v8::Object> options = info[0].As<v8::Object>();
if (options->Has(Nan::New("resolution").ToLocalChecked()))
{
v8::Local<v8::Value> bind_opt = options->Get(Nan::New("resolution").ToLocalChecked());
if (!bind_opt->IsNumber())
{
Nan::ThrowTypeError("'resolution' must be an Integer");
return;
}
resolution = bind_opt->IntegerValue();
if (resolution == 0)
{
Nan::ThrowTypeError("'resolution' can not be zero");
return;
}
}
if (options->Has(Nan::New("features").ToLocalChecked()))
{
v8::Local<v8::Value> bind_opt = options->Get(Nan::New("features").ToLocalChecked());
if (!bind_opt->IsBoolean())
{
Nan::ThrowTypeError("'features' must be an Boolean");
return;
}
add_features = bind_opt->BooleanValue();
}
}
try {
std::vector<node_mapnik::grid_line_type> lines;
std::vector<mapnik::grid::lookup_type> key_order;
node_mapnik::grid2utf<mapnik::grid>(*g->get(),lines,key_order,resolution);
// convert key order to proper javascript array
v8::Local<v8::Array> keys_a = Nan::New<v8::Array>(key_order.size());
std::vector<std::string>::iterator it;
unsigned int i;
for (it = key_order.begin(), i = 0; it < key_order.end(); ++it, ++i)
{
keys_a->Set(i, Nan::New<v8::String>(*it).ToLocalChecked());
}
mapnik::grid const& grid_type = *g->get();
// gather feature data
v8::Local<v8::Object> feature_data = Nan::New<v8::Object>();
if (add_features) {
node_mapnik::write_features<mapnik::grid>(*g->get(),
feature_data,
key_order);
}
// Create the return hash.
v8::Local<v8::Object> json = Nan::New<v8::Object>();
v8::Local<v8::Array> grid_array = Nan::New<v8::Array>();
unsigned array_size = std::ceil(grid_type.width()/static_cast<float>(resolution));
for (unsigned j=0;j<lines.size();++j)
{
node_mapnik::grid_line_type const & line = lines[j];
grid_array->Set(j, Nan::New<v8::String>(line.get(),array_size).ToLocalChecked());
}
json->Set(Nan::New("grid").ToLocalChecked(), grid_array);
json->Set(Nan::New("keys").ToLocalChecked(), keys_a);
json->Set(Nan::New("data").ToLocalChecked(), feature_data);
info.GetReturnValue().Set(json);
}
catch (std::exception const& ex)
{
// There is no known exception throws in the processing above
// so simply removing the following from coverage
/* LCOV_EXCL_START */
Nan::ThrowError(ex.what());
return;
/* LCOV_EXCL_STOP */
}
}
typedef struct {
uv_work_t request;
Grid* g;
bool error;
std::string error_name;
Nan::Persistent<v8::Function> cb;
std::vector<node_mapnik::grid_line_type> lines;
unsigned int resolution;
bool add_features;
std::vector<mapnik::grid::lookup_type> key_order;
} encode_grid_baton_t;
NAN_METHOD(Grid::encode)
{
Grid* g = Nan::ObjectWrap::Unwrap<Grid>(info.Holder());
// defaults
unsigned int resolution = 4;
bool add_features = true;
// options hash
if (info.Length() >= 1) {
if (!info[0]->IsObject())
{
Nan::ThrowTypeError("optional arg must be an options object");
return;
}
v8::Local<v8::Object> options = info[0].As<v8::Object>();
if (options->Has(Nan::New("resolution").ToLocalChecked()))
{
v8::Local<v8::Value> bind_opt = options->Get(Nan::New("resolution").ToLocalChecked());
if (!bind_opt->IsNumber())
{
Nan::ThrowTypeError("'resolution' must be an Integer");
return;
}
resolution = bind_opt->IntegerValue();
if (resolution == 0)
{
Nan::ThrowTypeError("'resolution' can not be zero");
return;
}
}
if (options->Has(Nan::New("features").ToLocalChecked()))
{
v8::Local<v8::Value> bind_opt = options->Get(Nan::New("features").ToLocalChecked());
if (!bind_opt->IsBoolean())
{
Nan::ThrowTypeError("'features' must be an Boolean");
return;
}
add_features = bind_opt->BooleanValue();
}
}
// ensure callback is a function
if (!info[info.Length()-1]->IsFunction())
{
Nan::ThrowTypeError("last argument must be a callback function");
return;
}
v8::Local<v8::Function> callback = v8::Local<v8::Function>::Cast(info[info.Length()-1]);
encode_grid_baton_t *closure = new encode_grid_baton_t();
closure->request.data = closure;
closure->g = g;
closure->error = false;
closure->resolution = resolution;
closure->add_features = add_features;
closure->cb.Reset(callback.As<v8::Function>());
// todo - reserve lines size?
uv_queue_work(uv_default_loop(), &closure->request, EIO_Encode, (uv_after_work_cb)EIO_AfterEncode);
g->Ref();
return;
}
void Grid::EIO_Encode(uv_work_t* req)
{
encode_grid_baton_t *closure = static_cast<encode_grid_baton_t *>(req->data);
try
{
node_mapnik::grid2utf<mapnik::grid>(*closure->g->get(),
closure->lines,
closure->key_order,
closure->resolution);
}
catch (std::exception const& ex)
{
// There is no known exception throws in the processing above
// so simply removing the following from coverage
/* LCOV_EXCL_START */
closure->error = true;
closure->error_name = ex.what();
/* LCOV_EXCL_STOP */
}
}
void Grid::EIO_AfterEncode(uv_work_t* req)
{
Nan::HandleScope scope;
encode_grid_baton_t *closure = static_cast<encode_grid_baton_t *>(req->data);
if (closure->error)
{
// There is no known ways to throw errors in the processing prior
// so simply removing the following from coverage
/* LCOV_EXCL_START */
v8::Local<v8::Value> argv[1] = { Nan::Error(closure->error_name.c_str()) };
Nan::MakeCallback(Nan::GetCurrentContext()->Global(), Nan::New(closure->cb), 1, argv);
/* LCOV_EXCL_STOP */
}
else
{
// convert key order to proper javascript array
v8::Local<v8::Array> keys_a = Nan::New<v8::Array>(closure->key_order.size());
std::vector<std::string>::iterator it;
unsigned int i;
for (it = closure->key_order.begin(), i = 0; it < closure->key_order.end(); ++it, ++i)
{
keys_a->Set(i, Nan::New<v8::String>(*it).ToLocalChecked());
}
mapnik::grid const& grid_type = *closure->g->get();
// gather feature data
v8::Local<v8::Object> feature_data = Nan::New<v8::Object>();
if (closure->add_features) {
node_mapnik::write_features<mapnik::grid>(grid_type,
feature_data,
closure->key_order);
}
// Create the return hash.
v8::Local<v8::Object> json = Nan::New<v8::Object>();
v8::Local<v8::Array> grid_array = Nan::New<v8::Array>(closure->lines.size());
unsigned array_size = std::ceil(grid_type.width()/static_cast<float>(closure->resolution));
for (unsigned j=0;j<closure->lines.size();++j)
{
node_mapnik::grid_line_type const & line = closure->lines[j];
grid_array->Set(j, Nan::New<v8::String>(line.get(),array_size).ToLocalChecked());
}
json->Set(Nan::New("grid").ToLocalChecked(), grid_array);
json->Set(Nan::New("keys").ToLocalChecked(), keys_a);
json->Set(Nan::New("data").ToLocalChecked(), feature_data);
v8::Local<v8::Value> argv[2] = { Nan::Null(), json };
Nan::MakeCallback(Nan::GetCurrentContext()->Global(), Nan::New(closure->cb), 2, argv);
}
closure->g->Unref();
closure->cb.Reset();
delete closure;
}
#endif
You can’t perform that action at this time.