#include "node_map.hpp" #include "node_file_source.hpp" #include #include #include #include namespace node_mbgl { struct NodeMap::RenderOptions { double zoom = 0; double bearing = 0; double latitude = 0; double longitude = 0; unsigned int width = 512; unsigned int height = 512; float ratio = 1.0f; std::vector classes; }; //////////////////////////////////////////////////////////////////////////////////////////////// // Static Node Methods v8::Persistent NodeMap::constructorTemplate; static std::shared_ptr sharedDisplay() { static auto display = std::make_shared(); return display; } const static char* releasedMessage() { return "Map resources have already been released"; } void NodeMap::Init(v8::Handle target) { NanScope(); v8::Local t = NanNew(New); t->InstanceTemplate()->SetInternalFieldCount(1); t->SetClassName(NanNew("Map")); NODE_SET_PROTOTYPE_METHOD(t, "load", Load); NODE_SET_PROTOTYPE_METHOD(t, "render", Render); NODE_SET_PROTOTYPE_METHOD(t, "release", Release); NanAssignPersistent(constructorTemplate, t); target->Set(NanNew("Map"), t->GetFunction()); // Initialize display connection on module load. sharedDisplay(); } NAN_METHOD(NodeMap::New) { NanScope(); if (!args.IsConstructCall()) { return NanThrowTypeError("Use the new operator to create new Map objects"); } if (args.Length() < 1 || !NanHasInstance(NodeFileSource::constructorTemplate, args[0])) { return NanThrowTypeError("Requires a FileSource as first argument"); } auto source = args[0]->ToObject(); // Check that request() and cancel() are defined. if (!source->Has(NanNew("request")) || !source->Get(NanNew("request"))->IsFunction()) { return NanThrowError("FileSource must have a request member function"); } if (!source->Has(NanNew("cancel")) || !source->Get(NanNew("cancel"))->IsFunction()) { return NanThrowError("FileSource must have a cancel member function"); } try { auto nodeMap = new NodeMap(source); nodeMap->Wrap(args.This()); } catch(std::exception &ex) { return NanThrowError(ex.what()); } NanReturnValue(args.This()); } const std::string StringifyStyle(v8::Handle styleHandle) { NanScope(); auto JSON = NanGetCurrentContext()->Global()->Get(NanNew("JSON"))->ToObject(); auto stringify = v8::Handle::Cast(JSON->Get(NanNew("stringify"))); return *NanUtf8String(stringify->Call(JSON, 1, &styleHandle)); } NAN_METHOD(NodeMap::Load) { NanScope(); auto nodeMap = node::ObjectWrap::Unwrap(args.Holder()); if (!nodeMap->isValid()) return NanThrowError(releasedMessage()); // Reset the flag as this could be the second time // we are calling this (being the previous successful). nodeMap->loaded = false; if (args.Length() < 1) { return NanThrowError("Requires a map style as first argument"); } std::string style; if (args[0]->IsObject()) { style = StringifyStyle(args[0]); } else if (args[0]->IsString()) { style = *NanUtf8String(args[0]); } else { return NanThrowTypeError("First argument must be a string or object"); } try { nodeMap->map->setStyleJSON(style, "."); } catch (const std::exception &ex) { return NanThrowError(ex.what()); } nodeMap->loaded = true; NanReturnUndefined(); } std::unique_ptr NodeMap::ParseOptions(v8::Local obj) { NanScope(); auto options = std::make_unique(); if (obj->Has(NanNew("zoom"))) { options->zoom = obj->Get(NanNew("zoom"))->NumberValue(); } if (obj->Has(NanNew("bearing"))) { options->bearing = obj->Get(NanNew("bearing"))->NumberValue(); } if (obj->Has(NanNew("center"))) { auto center = obj->Get(NanNew("center")).As(); if (center->Length() > 0) { options->latitude = center->Get(0)->NumberValue(); } if (center->Length() > 1) { options->longitude = center->Get(1)->NumberValue(); } } if (obj->Has(NanNew("width"))) { options->width = obj->Get(NanNew("width"))->IntegerValue(); } if (obj->Has(NanNew("height"))) { options->height = obj->Get(NanNew("height"))->IntegerValue(); } if (obj->Has(NanNew("ratio"))) { options->ratio = obj->Get(NanNew("ratio"))->IntegerValue(); } if (obj->Has(NanNew("classes"))) { auto classes = obj->Get(NanNew("classes"))->ToObject().As(); const int length = classes->Length(); options->classes.reserve(length); for (int i = 0; i < length; i++) { options->classes.push_back(std::string { *NanUtf8String(classes->Get(i)->ToString()) }); } } return options; } NAN_METHOD(NodeMap::Render) { NanScope(); auto nodeMap = node::ObjectWrap::Unwrap(args.Holder()); if (!nodeMap->isValid()) return NanThrowError(releasedMessage()); if (args.Length() <= 0 || !args[0]->IsObject()) { return NanThrowTypeError("First argument must be an options object"); } if (args.Length() <= 1 || !args[1]->IsFunction()) { return NanThrowTypeError("Second argument must be a callback function"); } if (!nodeMap->isLoaded()) { return NanThrowTypeError("Style is not loaded"); } auto options = ParseOptions(args[0]->ToObject()); assert(!nodeMap->callback); assert(!nodeMap->image); nodeMap->callback = std::unique_ptr(new NanCallback(args[1].As())); try { nodeMap->startRender(std::move(options)); } catch (mbgl::util::Exception &ex) { return NanThrowError(ex.what()); } NanReturnUndefined(); } void NodeMap::startRender(std::unique_ptr options) { map->resize(options->width, options->height, options->ratio); map->setClasses(options->classes); map->setLatLngZoom(mbgl::LatLng(options->latitude, options->longitude), options->zoom); map->setBearing(options->bearing); map->renderStill([this](const std::exception_ptr eptr, std::unique_ptr result) { if (eptr) { error = std::move(eptr); uv_async_send(async); } else { assert(!image); image = std::move(result); 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(async)); } void NodeMap::renderFinished() { NanScope(); // We're done with this render call, so we're unrefing so that the loop could close. uv_unref(reinterpret_cast(async)); // There is no render pending anymore, we the GC could now delete this object if it went out // of scope. Unref(); // Move the callback and image out of the way so that the callback can start a new render call. auto cb = std::move(callback); auto img = std::move(image); assert(cb); // These have to be empty to be prepared for the next render call. assert(!callback); assert(!image); if (error) { std::string errorMessage; try { std::rethrow_exception(error); } catch (const std::exception& ex) { errorMessage = ex.what(); } v8::Local argv[] = { NanError(errorMessage.c_str()) }; // This must be empty to be prepared for the next render call. error = nullptr; assert(!error); cb->Call(1, argv); } else if (img) { auto result = NanNew(); result->Set(NanNew("width"), NanNew(img->width)); result->Set(NanNew("height"), NanNew(img->height)); v8::Local pixels = NanNewBufferHandle( reinterpret_cast(img->pixels.get()), size_t(img->width) * size_t(img->height) * sizeof(mbgl::StillImage::Pixel), // Retain the StillImage object until the buffer is deleted. [](char *, void *hint) { delete reinterpret_cast(hint); }, const_cast(img.get()) ); img.release(); result->Set(NanNew("pixels"), pixels); v8::Local argv[] = { NanNull(), result, }; cb->Call(2, argv); } else { v8::Local argv[] = { NanError("Didn't get an image") }; cb->Call(1, argv); } } NAN_METHOD(NodeMap::Release) { NanScope(); auto nodeMap = node::ObjectWrap::Unwrap(args.Holder()); if (!nodeMap->isValid()) return NanThrowError(releasedMessage()); try { nodeMap->release(); } catch (const std::exception &ex) { return NanThrowError(ex.what()); } NanReturnUndefined(); } void NodeMap::release() { if (!isValid()) throw mbgl::util::Exception(releasedMessage()); valid = false; NanDisposePersistent(source); uv_close(reinterpret_cast(async), [] (uv_handle_t *handle) { delete reinterpret_cast(handle); }); map.reset(nullptr); } //////////////////////////////////////////////////////////////////////////////////////////////// // Instance NodeMap::NodeMap(v8::Handle source_) : view(sharedDisplay()), fs(*ObjectWrap::Unwrap(source_)), map(std::make_unique(view, fs, mbgl::MapMode::Still)), async(new uv_async_t) { NanAssignPersistent(source, source_); async->data = this; #if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10 uv_async_init(uv_default_loop(), async, [](uv_async_t *as, int) { #else uv_async_init(uv_default_loop(), async, [](uv_async_t *as) { #endif reinterpret_cast(as->data)->renderFinished(); }); // Make sure the async handle doesn't keep the loop alive. uv_unref(reinterpret_cast(async)); } NodeMap::~NodeMap() { if (valid) release(); } }