Node platform improvements (#891)

This commit is contained in:
tdcosta100
2023-03-09 04:27:14 +00:00
committed by GitHub
parent 9a3afedb25
commit 9766f6fcc2
5 changed files with 212 additions and 14 deletions

View File

@@ -5,6 +5,7 @@
- *...Add new stuff here...*
# 5.2.0
* Node platform improvements (added setSize and a new render call without render options object) by @tdcosta100 in https://github.com/maplibre/maplibre-gl-native/pull/891
* Move node ci+release to self hosted Ubuntu arm64 by @acalcutt in https://github.com/maplibre/maplibre-gl-native/pull/873
* Add windows support by @tdcosta100 in https://github.com/maplibre/maplibre-gl-native/pull/707
* Add Typings for Node Platform by @KiwiKilian in https://github.com/maplibre/maplibre-gl-native/pull/766

View File

@@ -173,6 +173,8 @@ var map = new mbgl.Map({
response.data = body;
callback(null, response);
} else if (res.statusCode == 204) {
callback();
} else {
callback(new Error(JSON.parse(body).message));
}

View File

@@ -102,6 +102,7 @@ ParseError)JS").ToLocalChecked()).ToLocalChecked();
Nan::SetPrototypeMethod(tpl, "setLayoutProperty", SetLayerProperty);
Nan::SetPrototypeMethod(tpl, "setPaintProperty", SetLayerProperty);
Nan::SetPrototypeMethod(tpl, "setFilter", SetFilter);
Nan::SetPrototypeMethod(tpl, "setSize", SetSize);
Nan::SetPrototypeMethod(tpl, "setCenter", SetCenter);
Nan::SetPrototypeMethod(tpl, "setZoom", SetZoom);
Nan::SetPrototypeMethod(tpl, "setBearing", SetBearing);
@@ -426,11 +427,11 @@ void NodeMap::Render(const Nan::FunctionCallbackInfo<v8::Value>& info) {
auto nodeMap = Nan::ObjectWrap::Unwrap<NodeMap>(info.Holder());
if (!nodeMap->map) return Nan::ThrowError(releasedMessage());
if (info.Length() <= 0 || !info[0]->IsObject()) {
return Nan::ThrowTypeError("First argument must be an options object");
if (info.Length() <= 0 || (!info[0]->IsObject() && !info[0]->IsFunction())) {
return Nan::ThrowTypeError("First argument must be an options object or a callback function");
}
if (info.Length() <= 1 || !info[1]->IsFunction()) {
if ((info.Length() <= 1 && !info[0]->IsFunction()) || (info.Length() > 1 && !info[1]->IsFunction())) {
return Nan::ThrowTypeError("Second argument must be a callback function");
}
@@ -443,12 +444,20 @@ void NodeMap::Render(const Nan::FunctionCallbackInfo<v8::Value>& info) {
}
try {
auto options = ParseOptions(Nan::To<v8::Object>(info[0]).ToLocalChecked());
assert(!nodeMap->req);
assert(!nodeMap->image.data);
nodeMap->req = std::make_unique<RenderRequest>(Nan::To<v8::Function>(info[1]).ToLocalChecked());
if (info[0]->IsFunction()) {
assert(!nodeMap->req);
assert(!nodeMap->image.data);
nodeMap->req = std::make_unique<RenderRequest>(Nan::To<v8::Function>(info[0]).ToLocalChecked());
nodeMap->startRender(options);
nodeMap->startRender();
} else {
auto options = ParseOptions(Nan::To<v8::Object>(info[0]).ToLocalChecked());
assert(!nodeMap->req);
assert(!nodeMap->image.data);
nodeMap->req = std::make_unique<RenderRequest>(Nan::To<v8::Function>(info[1]).ToLocalChecked());
nodeMap->startRender(options);
}
} catch (const mbgl::style::conversion::Error& err) {
return Nan::ThrowTypeError(err.message.c_str());
} catch (const mbgl::util::StyleParseException& ex) {
@@ -460,6 +469,27 @@ void NodeMap::Render(const Nan::FunctionCallbackInfo<v8::Value>& info) {
info.GetReturnValue().SetUndefined();
}
void NodeMap::startRender() {
map->renderStill([this](const std::exception_ptr& eptr) {
if (eptr) {
error = eptr;
uv_async_send(async);
} else {
assert(!image.data);
image = frontend->readStillImage();
uv_async_send(async);
}
});
// Retain this object, otherwise it might get destructed before we are finished rendering the
// still image.
Ref();
// Similarly, we're now waiting for the async to be called, so we need to make sure that it
// keeps the loop alive.
uv_ref(reinterpret_cast<uv_handle_t*>(async));
}
void NodeMap::startRender(const NodeMap::RenderOptions& options) {
frontend->setSize(options.size);
map->setSize(options.size);
@@ -640,7 +670,7 @@ void NodeMap::cancel() {
reinterpret_cast<NodeMap *>(h->data)->renderFinished();
});
frontend = std::make_unique<mbgl::HeadlessFrontend>(mbgl::Size{ 256, 256 }, pixelRatio);
frontend = std::make_unique<mbgl::HeadlessFrontend>(mbgl::Size{ 512, 512 }, pixelRatio);
map = std::make_unique<mbgl::Map>(*frontend, mapObserver,
mbgl::MapOptions().withSize(frontend->getSize())
.withPixelRatio(pixelRatio)
@@ -925,6 +955,30 @@ void NodeMap::SetFilter(const Nan::FunctionCallbackInfo<v8::Value>& info) {
layer->setFilter(filter);
}
void NodeMap::SetSize(const Nan::FunctionCallbackInfo<v8::Value>& info) {
auto nodeMap = Nan::ObjectWrap::Unwrap<NodeMap>(info.Holder());
if (!nodeMap->map) return Nan::ThrowError(releasedMessage());
if (info.Length() <= 0 || !info[0]->IsArray()) {
return Nan::ThrowTypeError("First argument must be an array");
}
auto size = info[0].As<v8::Array>();
uint32_t width= 0;
uint32_t height = 0;
if (size->Length() > 0) { width = Nan::To<uint32_t>(Nan::Get(size, 0).ToLocalChecked()).ToChecked(); }
if (size->Length() > 1) { height = Nan::To<uint32_t>(Nan::Get(size, 1).ToLocalChecked()).ToChecked(); }
try {
nodeMap->frontend->setSize({ width, height });
nodeMap->map->setSize({ width, height });
} catch (const std::exception &ex) {
return Nan::ThrowError(ex.what());
}
info.GetReturnValue().SetUndefined();
}
void NodeMap::SetCenter(const Nan::FunctionCallbackInfo<v8::Value>& info) {
auto nodeMap = Nan::ObjectWrap::Unwrap<NodeMap>(info.Holder());
if (!nodeMap->map) return Nan::ThrowError(releasedMessage());
@@ -1419,7 +1473,7 @@ NodeMap::NodeMap(v8::Local<v8::Object> options)
: true;
}())
, mapObserver(NodeMapObserver())
, frontend(std::make_unique<mbgl::HeadlessFrontend>(mbgl::Size { 256, 256 }, pixelRatio))
, frontend(std::make_unique<mbgl::HeadlessFrontend>(mbgl::Size { 512, 512 }, pixelRatio))
, map(std::make_unique<mbgl::Map>(*frontend, mapObserver,
mbgl::MapOptions().withSize(frontend->getSize())
.withPixelRatio(pixelRatio)

View File

@@ -55,6 +55,7 @@ public:
static void SetLayerZoomRange(const Nan::FunctionCallbackInfo<v8::Value>&);
static void SetLayerProperty(const Nan::FunctionCallbackInfo<v8::Value>&);
static void SetFilter(const Nan::FunctionCallbackInfo<v8::Value>&);
static void SetSize(const Nan::FunctionCallbackInfo<v8::Value>&);
static void SetCenter(const Nan::FunctionCallbackInfo<v8::Value>&);
static void SetZoom(const Nan::FunctionCallbackInfo<v8::Value>&);
static void SetBearing(const Nan::FunctionCallbackInfo<v8::Value>&);
@@ -72,6 +73,7 @@ public:
static v8::Local<v8::Value> ParseError(const char* msg);
void startRender();
void startRender(const RenderOptions& options);
void renderFinished();

View File

@@ -118,6 +118,7 @@ test('Map', function(t) {
'setLayoutProperty',
'setPaintProperty',
'setFilter',
'setSize',
'setCenter',
'setZoom',
'setBearing',
@@ -415,16 +416,16 @@ test('Map', function(t) {
ratio: 1
};
t.test('requires an object as the first parameter', function(t) {
t.test('requires an object or a callback function as the first parameter', function(t) {
var map = new mbgl.Map(options);
t.throws(function() {
map.render();
}, /First argument must be an options object/);
}, /First argument must be an options object or a callback function/);
t.throws(function() {
map.render('invalid');
}, /First argument must be an options object/);
}, /First argument must be an options object or a callback function/);
map.release();
t.end();
@@ -456,6 +457,17 @@ test('Map', function(t) {
t.end();
});
t.test('requires a style to be set (without render options object)', function(t) {
var map = new mbgl.Map(options);
t.throws(function() {
map.render(function() {});
}, /Style is not loaded/);
map.release();
t.end();
});
t.test('returns an error delayed', function(t) {
var delay = 0;
var map = new mbgl.Map({
@@ -476,6 +488,27 @@ test('Map', function(t) {
});
});
t.test('returns an error delayed (without render options object)', function(t) {
var delay = 0;
var map = new mbgl.Map({
request: function(req, callback) {
delay += 100;
setTimeout(function() {
callback(new Error('not found'));
}, delay);
},
ratio: 1
});
map.load(style);
map.setZoom(1);
map.render(function(err, data) {
map.release();
t.ok(err, 'returns error');
t.end();
});
});
t.test('returns an error', function(t) {
var map = new mbgl.Map(options);
map.load(style);
@@ -487,6 +520,18 @@ test('Map', function(t) {
});
});
t.test('returns an error (without render options object)', function(t) {
var map = new mbgl.Map(options);
map.load(style);
map.setZoom(1);
map.render(function(err, data) {
map.release();
t.ok(err, 'returns error');
t.end();
});
});
t.test('double release', function(t) {
var map = new mbgl.Map(options);
map.release();
@@ -511,6 +556,19 @@ test('Map', function(t) {
});
});
t.test('returns an image (without render options object)', function(t) {
var map = new mbgl.Map(options);
map.load(style);
map.render(function(err, pixels) {
t.error(err);
map.release();
t.ok(pixels);
t.ok(pixels instanceof Buffer);
t.equal(pixels.length, 512 * 512 * 4)
t.end();
});
});
t.test('can be called several times in serial', function(t) {
var completed = 0;
var remaining = 10;
@@ -536,6 +594,31 @@ test('Map', function(t) {
render();
});
t.test('can be called several times in serial (without render options object)', function(t) {
var completed = 0;
var remaining = 10;
var start = +new Date;
var map = new mbgl.Map(options);
map.load(style);
function render() {
map.render(function(err, data) {
t.error(err);
t.ok(true, 'render @ ' + ((+new Date) - start) + 'ms');
if (++completed === remaining) {
map.release();
t.end();
} else {
render();
}
});
}
render();
});
t.test('throws if called in parallel', function(t) {
var map = new mbgl.Map(options);
map.load(style);
@@ -549,6 +632,19 @@ test('Map', function(t) {
t.end();
});
t.test('throws if called in parallel (without render options object)', function(t) {
var map = new mbgl.Map(options);
map.load(style);
t.throws(function() {
map.render(function() {});
map.render(function() {});
}, /Map is currently processing a RenderRequest/);
map.release();
t.end();
});
// This can't be tested with a test-suite render test because zoom and center
// are set via a different code path when included as style properties.
t.test('sets zoom before center', function(t) {
@@ -589,7 +685,50 @@ test('Map', function(t) {
t.equal(numPixels, 0);
t.end();
});
})
});
t.test('sets zoom before center (without render options object)', function(t) {
var map = new mbgl.Map(options);
map.load({
"version": 8,
"sources": {
"geojson": {
"type": "geojson",
"data": {
"type": "Point",
"coordinates": [
18.05489,
59.32744
]
}
}
},
"layers": [
{
"id": "circle",
"type": "circle",
"source": "geojson",
"paint": {
"circle-color": "red"
}
}
]
});
map.setSize([400, 400]);
map.setZoom(5);
map.setCenter([18.05489, 59.32744]);
map.render(function(err, actual) {
t.error(err);
var PNG = require('pngjs').PNG;
var pixelmatch = require('pixelmatch');
var expected = PNG.sync.read(
fs.readFileSync(path.join(__dirname, '../fixtures/zoom-center/expected.png'))).data;
var numPixels = pixelmatch(actual, expected, undefined, 400, 400, { threshold: 0.13 });
t.equal(numPixels, 0);
t.end();
});
});
});
t.test('request callback', function (t) {