initial version with updated build system
This commit is contained in:
18
.clang-format
Normal file
18
.clang-format
Normal file
@@ -0,0 +1,18 @@
|
||||
Standard: Cpp11
|
||||
IndentWidth: 4
|
||||
AccessModifierOffset: -4
|
||||
UseTab: Never
|
||||
BinPackParameters: false
|
||||
AllowShortIfStatementsOnASingleLine: false
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
AllowShortBlocksOnASingleLine: false
|
||||
AllowShortFunctionsOnASingleLine: false
|
||||
ConstructorInitializerAllOnOneLineOrOnePerLine: true
|
||||
AlwaysBreakTemplateDeclarations: true
|
||||
NamespaceIndentation: None
|
||||
PointerBindsToType: false
|
||||
SpacesInParentheses: false
|
||||
BreakBeforeBraces: Attach
|
||||
ColumnLimit: 100
|
||||
Cpp11BracedListStyle: false
|
||||
SpacesBeforeTrailingComments: 1
|
||||
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
/build
|
||||
/lib
|
||||
/node_modules
|
||||
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
[submodule "vendor/mbgl"]
|
||||
path = vendor/mbgl
|
||||
url = https://github.com/mapbox/mapbox-gl-native.git
|
||||
43
Makefile
Normal file
43
Makefile
Normal file
@@ -0,0 +1,43 @@
|
||||
BUILDTYPE ?= Release
|
||||
MBGL ?= vendor/mbgl
|
||||
|
||||
DEBUG_FLAG =
|
||||
ifeq ($(BUILDTYPE), Debug)
|
||||
DEBUG_FLAG = -d
|
||||
endif
|
||||
|
||||
ifeq ($(shell uname -s), Darwin)
|
||||
HOST ?= osx
|
||||
endif
|
||||
HOST ?= linux
|
||||
|
||||
|
||||
include $(MBGL)/config/defaults.mk
|
||||
|
||||
global: build
|
||||
|
||||
.PHONY: build
|
||||
build: build/Makefile
|
||||
@node-gyp build $(DEBUG_FLAG) -- -j8
|
||||
|
||||
.PHONY: build/Makefile
|
||||
build/Makefile: $(MBGL)/config/$(HOST).gypi
|
||||
@node-gyp configure -- \
|
||||
-Dmbgl=$(MBGL) \
|
||||
-Dhost=osx \
|
||||
-I$(MBGL)/config/$(HOST).gypi \
|
||||
$(LIBS_$(HOST)) \
|
||||
-Duv_static_libs= -Duv_ldflags= \
|
||||
-Goutput_dir=.
|
||||
|
||||
$(MBGL)/config/%.gypi: $(MBGL)/configure
|
||||
make -C $(MBGL) config/$*.gypi
|
||||
|
||||
.PHONY: test
|
||||
test: build
|
||||
@`npm bin`/mocha -R spec
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
rm -rf build
|
||||
rm $(MBGL)/config.gypi
|
||||
59
binding.gyp
Normal file
59
binding.gyp
Normal file
@@ -0,0 +1,59 @@
|
||||
{
|
||||
'targets': [
|
||||
{ 'target_name': 'mbgl',
|
||||
'dependencies': [
|
||||
'./<(mbgl)/mbgl.gyp:core',
|
||||
'./<(mbgl)/mbgl.gyp:platform-<(platform_lib)',
|
||||
'./<(mbgl)/mbgl.gyp:headless-<(headless_lib)',
|
||||
],
|
||||
|
||||
'include_dirs': [ "<!(node -e \"require('nan')\")" ],
|
||||
|
||||
'sources': [
|
||||
'src/mbgl.cpp',
|
||||
'src/node_file_source.hpp',
|
||||
'src/node_file_source.cpp',
|
||||
'src/node_map.hpp',
|
||||
'src/node_map.cpp',
|
||||
'src/node_map_render_worker.hpp',
|
||||
'src/node_map_render_worker.cpp',
|
||||
'src/node_request.hpp',
|
||||
'src/node_request.cpp',
|
||||
],
|
||||
|
||||
'variables': {
|
||||
'cflags_cc': [
|
||||
'-std=c++11',
|
||||
'-stdlib=libc++',
|
||||
'-Wno-unused-parameter'
|
||||
],
|
||||
},
|
||||
|
||||
'conditions': [
|
||||
['OS == "mac"', {
|
||||
'xcode_settings': {
|
||||
'MACOSX_DEPLOYMENT_TARGET': '10.7',
|
||||
'OTHER_CPLUSPLUSFLAGS': [ '<@(cflags_cc)' ],
|
||||
'GCC_ENABLE_CPP_EXCEPTIONS': 'YES'
|
||||
}
|
||||
}, {
|
||||
'cflags_cc': [
|
||||
'<@(cflags_cc)',
|
||||
'-fexceptions',
|
||||
],
|
||||
}]
|
||||
],
|
||||
},
|
||||
|
||||
{ 'target_name': 'action_after_build',
|
||||
'type': 'none',
|
||||
'dependencies': [ 'mbgl' ],
|
||||
'copies': [
|
||||
{
|
||||
'files': [ '<(PRODUCT_DIR)/mbgl.node' ],
|
||||
'destination': 'lib'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
16
package.json
Normal file
16
package.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "mbgl",
|
||||
"version": "0.0.1",
|
||||
"main": "lib/mbgl.node",
|
||||
"dependencies": {
|
||||
"mocha": "^2.1.0",
|
||||
"nan": "^1.4.1",
|
||||
"request": "^2.51.0"
|
||||
},
|
||||
"devDependencies": {},
|
||||
"scripts": {
|
||||
"install": "make",
|
||||
"test": "mocha -R spec test"
|
||||
},
|
||||
"gypfile": true
|
||||
}
|
||||
26
src/mbgl.cpp
Normal file
26
src/mbgl.cpp
Normal file
@@ -0,0 +1,26 @@
|
||||
#include <node.h>
|
||||
#include <nan.h>
|
||||
|
||||
#include "node_file_source.hpp"
|
||||
#include "node_map.hpp"
|
||||
#include "node_request.hpp"
|
||||
|
||||
void RegisterModule(v8::Handle<v8::Object> exports) {
|
||||
NanScope();
|
||||
|
||||
node_mbgl::NodeFileSource::Init(exports);
|
||||
node_mbgl::NodeMap::Init(exports);
|
||||
node_mbgl::NodeRequest::Init(exports);
|
||||
|
||||
// Exports Resource constants.
|
||||
auto ConstantProperty = static_cast<v8::PropertyAttribute>(v8::ReadOnly | v8::DontDelete);
|
||||
auto resource = v8::Object::New();
|
||||
resource->ForceSet(NanNew("Unknown"), NanNew(mbgl::Resource::Unknown), ConstantProperty);
|
||||
resource->ForceSet(NanNew("Tile"), NanNew(mbgl::Resource::Tile), ConstantProperty);
|
||||
resource->ForceSet(NanNew("Glyphs"), NanNew(mbgl::Resource::Glyphs), ConstantProperty);
|
||||
resource->ForceSet(NanNew("Image"), NanNew(mbgl::Resource::Image), ConstantProperty);
|
||||
resource->ForceSet(NanNew("JSON"), NanNew(mbgl::Resource::JSON), ConstantProperty);
|
||||
exports->ForceSet(NanNew("Resource"), resource, ConstantProperty);
|
||||
}
|
||||
|
||||
NODE_MODULE(mbgl, RegisterModule)
|
||||
156
src/node_file_source.cpp
Normal file
156
src/node_file_source.cpp
Normal file
@@ -0,0 +1,156 @@
|
||||
#include "node_file_source.hpp"
|
||||
#include "node_request.hpp"
|
||||
|
||||
#include <mbgl/storage/default/request.hpp>
|
||||
#include <mbgl/util/async_queue.hpp>
|
||||
|
||||
namespace node_mbgl {
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Static Node Methods
|
||||
|
||||
v8::Persistent<v8::FunctionTemplate> NodeFileSource::constructorTemplate;
|
||||
|
||||
void NodeFileSource::Init(v8::Handle<v8::Object> target) {
|
||||
NanScope();
|
||||
|
||||
v8::Local<v8::FunctionTemplate> t = NanNew<v8::FunctionTemplate>(New);
|
||||
|
||||
t->InstanceTemplate()->SetInternalFieldCount(1);
|
||||
t->SetClassName(NanNew("FileSource"));
|
||||
|
||||
NanAssignPersistent(constructorTemplate, t);
|
||||
|
||||
target->Set(NanNew("FileSource"), t->GetFunction());
|
||||
}
|
||||
|
||||
NAN_METHOD(NodeFileSource::New) {
|
||||
NanScope();
|
||||
|
||||
if (!args.IsConstructCall()) {
|
||||
return NanThrowTypeError("Use the new operator to create new FileSource objects");
|
||||
}
|
||||
|
||||
auto fs = new NodeFileSource();
|
||||
fs->Wrap(args.This());
|
||||
|
||||
NanReturnValue(args.This());
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Instance
|
||||
|
||||
struct NodeFileSource::Action {
|
||||
const enum : bool { Add, Cancel } type;
|
||||
mbgl::Request *const request;
|
||||
};
|
||||
|
||||
NodeFileSource::NodeFileSource() :
|
||||
queue(new Queue(uv_default_loop(), [this](Action &action) {
|
||||
if (action.type == Action::Add) {
|
||||
processAdd(action.request);
|
||||
} else if (action.type == Action::Cancel) {
|
||||
processCancel(action.request);
|
||||
}
|
||||
}))
|
||||
{
|
||||
// Make sure that the queue doesn't block the loop from exiting.
|
||||
queue->unref();
|
||||
}
|
||||
|
||||
NodeFileSource::~NodeFileSource() {
|
||||
queue->stop();
|
||||
queue = nullptr;
|
||||
}
|
||||
|
||||
mbgl::Request *NodeFileSource::request(const mbgl::Resource& resource, uv_loop_t *loop, Callback callback) {
|
||||
auto request = new mbgl::Request(resource, loop, std::move(callback));
|
||||
|
||||
// This function can be called from any thread. Make sure we're executing the actual call in the
|
||||
// file source loop by sending it over the queue. It will be processed in processAction().
|
||||
queue->send(Action{ Action::Add, request });
|
||||
return request;
|
||||
}
|
||||
|
||||
void NodeFileSource::request(const mbgl::Resource &resource, Callback callback) {
|
||||
auto request = new mbgl::Request(resource, nullptr, std::move(callback));
|
||||
|
||||
// This function can be called from any thread. Make sure we're executing the actual call in the
|
||||
// file source loop by sending it over the queue. It will be processed in processAction().
|
||||
queue->send(Action{ Action::Add, request });
|
||||
}
|
||||
|
||||
|
||||
void NodeFileSource::cancel(mbgl::Request *request) {
|
||||
request->cancel();
|
||||
|
||||
// This function can be called from any thread. Make sure we're executing the actual call in the
|
||||
// file source loop by sending it over the queue. It will be processed in processAction().
|
||||
queue->send(Action{ Action::Cancel, request });
|
||||
}
|
||||
|
||||
|
||||
void NodeFileSource::processAdd(mbgl::Request *request) {
|
||||
NanScope();
|
||||
|
||||
// Make sure the loop stays alive as long as request is pending.
|
||||
if (pending.empty()) {
|
||||
queue->ref();
|
||||
}
|
||||
|
||||
auto requestHandle = v8::Local<v8::Object>::New(NodeRequest::Create(handle_, request));
|
||||
pending.emplace(request, std::move(v8::Persistent<v8::Object>::New(requestHandle)));
|
||||
|
||||
v8::Local<v8::Value> argv[] = { requestHandle };
|
||||
NanMakeCallback(handle_, NanNew("request"), 1, argv);
|
||||
}
|
||||
|
||||
void NodeFileSource::processCancel(mbgl::Request *request) {
|
||||
NanScope();
|
||||
|
||||
auto it = pending.find(request);
|
||||
if (it == pending.end()) {
|
||||
// The response callback was already fired. There is no point in calling the cancelation
|
||||
// callback because the request is already completed.
|
||||
} else {
|
||||
auto requestHandle = v8::Local<v8::Object>::New(it->second);
|
||||
|
||||
// Dispose and remove the persistent handle
|
||||
it->second.Dispose();
|
||||
pending.erase(it);
|
||||
|
||||
// Make sure the the loop can exit when there are no pending requests.
|
||||
if (pending.empty()) {
|
||||
queue->unref();
|
||||
}
|
||||
|
||||
if (handle_->Has(NanNew("cancel"))) {
|
||||
v8::Local<v8::Value> argv[] = { requestHandle };
|
||||
NanMakeCallback(handle_, NanNew("cancel"), 1, argv);
|
||||
}
|
||||
|
||||
// Set the request handle in the request wrapper handle to null
|
||||
ObjectWrap::Unwrap<NodeRequest>(requestHandle)->cancel();
|
||||
}
|
||||
|
||||
// Finally, destruct the request object
|
||||
request->destruct();
|
||||
}
|
||||
|
||||
void NodeFileSource::notify(mbgl::Request *request, const std::shared_ptr<const mbgl::Response>& response) {
|
||||
// First, remove the request, since it might be destructed at any point now.
|
||||
auto it = pending.find(request);
|
||||
if (it != pending.end()) {
|
||||
it->second.Dispose();
|
||||
pending.erase(it);
|
||||
|
||||
// Make sure the the loop can exit when there are no pending requests.
|
||||
if (pending.empty()) {
|
||||
queue->unref();
|
||||
}
|
||||
}
|
||||
|
||||
request->notify(response);
|
||||
}
|
||||
|
||||
}
|
||||
49
src/node_file_source.hpp
Normal file
49
src/node_file_source.hpp
Normal file
@@ -0,0 +1,49 @@
|
||||
#pragma once
|
||||
|
||||
#include <mbgl/storage/file_source.hpp>
|
||||
|
||||
#include <node.h>
|
||||
#include <nan.h>
|
||||
|
||||
#include <map>
|
||||
|
||||
namespace mbgl { namespace util { template <typename T> class AsyncQueue; } }
|
||||
|
||||
namespace node_mbgl {
|
||||
|
||||
class NodeFileSource : public node::ObjectWrap, public mbgl::FileSource {
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Static Node Methods
|
||||
public:
|
||||
static void Init(v8::Handle<v8::Object> target);
|
||||
static NAN_METHOD(New);
|
||||
|
||||
static v8::Persistent<v8::FunctionTemplate> constructorTemplate;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Instance
|
||||
public:
|
||||
NodeFileSource();
|
||||
~NodeFileSource();
|
||||
|
||||
mbgl::Request *request(const mbgl::Resource &resource, uv_loop_t *loop, Callback callback);
|
||||
void request(const mbgl::Resource &resource, Callback callback);
|
||||
void cancel(mbgl::Request *request);
|
||||
|
||||
|
||||
// visiblity?
|
||||
void notify(mbgl::Request *request, const std::shared_ptr<const mbgl::Response> &response);
|
||||
|
||||
private:
|
||||
struct Action;
|
||||
using Queue = mbgl::util::AsyncQueue<Action>;
|
||||
|
||||
void processAdd(mbgl::Request *request);
|
||||
void processCancel(mbgl::Request *request);
|
||||
|
||||
private:
|
||||
std::map<mbgl::Request *, v8::Persistent<v8::Object>> pending;
|
||||
Queue *queue = nullptr;
|
||||
};
|
||||
|
||||
}
|
||||
185
src/node_map.cpp
Normal file
185
src/node_map.cpp
Normal file
@@ -0,0 +1,185 @@
|
||||
#include "node_map.hpp"
|
||||
#include "node_map_render_worker.hpp"
|
||||
#include "node_file_source.hpp"
|
||||
|
||||
#include <mbgl/platform/default/headless_display.hpp>
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
namespace node_mbgl {
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Static Node Methods
|
||||
|
||||
v8::Persistent<v8::FunctionTemplate> NodeMap::constructorTemplate;
|
||||
|
||||
void NodeMap::Init(v8::Handle<v8::Object> target) {
|
||||
NanScope();
|
||||
|
||||
v8::Local<v8::FunctionTemplate> t = NanNew<v8::FunctionTemplate>(New);
|
||||
|
||||
t->InstanceTemplate()->SetInternalFieldCount(1);
|
||||
t->SetClassName(NanNew("Map"));
|
||||
|
||||
NODE_SET_PROTOTYPE_METHOD(t, "load", Load);
|
||||
NODE_SET_PROTOTYPE_METHOD(t, "render", Render);
|
||||
|
||||
NanAssignPersistent(constructorTemplate, t);
|
||||
|
||||
target->Set(NanNew("Map"), t->GetFunction());
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
auto map = new NodeMap(args[0]->ToObject());
|
||||
map->Wrap(args.This());
|
||||
|
||||
NanReturnValue(args.This());
|
||||
}
|
||||
|
||||
const std::string StringifyStyle(v8::Handle<v8::Value> styleHandle) {
|
||||
NanScope();
|
||||
|
||||
auto JSON = NanGetCurrentContext()->Global()->Get(NanNew("JSON"))->ToObject();
|
||||
auto stringify = v8::Handle<v8::Function>::Cast(JSON->Get(NanNew("stringify")));
|
||||
|
||||
return *NanUtf8String(stringify->Call(JSON, 1, &styleHandle));
|
||||
}
|
||||
|
||||
NAN_METHOD(NodeMap::Load) {
|
||||
NanScope();
|
||||
|
||||
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()) {
|
||||
v8::Local<v8::String> toStr = args[0]->ToString();
|
||||
style.resize(toStr->Utf8Length());
|
||||
toStr->WriteUtf8(const_cast<char *>(style.data()));
|
||||
} else {
|
||||
return NanThrowTypeError("First argument must be a string or object");
|
||||
}
|
||||
|
||||
auto nodeMap = node::ObjectWrap::Unwrap<NodeMap>(args.Holder());
|
||||
|
||||
try {
|
||||
nodeMap->map.setStyleJSON(style, ".");
|
||||
} catch (const std::exception &ex) {
|
||||
NanThrowError(ex.what());
|
||||
}
|
||||
|
||||
NanReturnUndefined();
|
||||
}
|
||||
|
||||
std::unique_ptr<NodeMap::RenderOptions> NodeMap::ParseOptions(v8::Local<v8::Object> obj) {
|
||||
NanScope();
|
||||
|
||||
auto options = mbgl::util::make_unique<RenderOptions>();
|
||||
|
||||
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<v8::Array>();
|
||||
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("accessToken"))) {
|
||||
options->accessToken = *NanUtf8String(obj->Get(NanNew("accessToken")->ToString()));
|
||||
}
|
||||
|
||||
if (obj->Has(NanNew("classes"))) {
|
||||
auto classes = obj->Get(NanNew("classes"))->ToObject().As<v8::Array>();
|
||||
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();
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
auto nodeMap = node::ObjectWrap::Unwrap<NodeMap>(args.Holder());
|
||||
|
||||
const bool wasEmpty = nodeMap->queue_.empty();
|
||||
|
||||
nodeMap->queue_.push(mbgl::util::make_unique<RenderWorker>(
|
||||
nodeMap,
|
||||
ParseOptions(args[0]->ToObject()),
|
||||
new NanCallback(args[1].As<v8::Function>())));
|
||||
|
||||
if (wasEmpty) {
|
||||
// When the queue was empty, there was no action in progress, so we can start a new one.
|
||||
NanAsyncQueueWorker(nodeMap->queue_.front().release());
|
||||
}
|
||||
|
||||
NanReturnUndefined();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Instance
|
||||
|
||||
std::shared_ptr<mbgl::HeadlessDisplay> sharedDisplay() {
|
||||
static auto display = std::make_shared<mbgl::HeadlessDisplay>();
|
||||
return display;
|
||||
}
|
||||
|
||||
NodeMap::NodeMap(v8::Handle<v8::Object> source_) :
|
||||
view(sharedDisplay()),
|
||||
fs(*ObjectWrap::Unwrap<NodeFileSource>(source_)),
|
||||
map(view, fs) {
|
||||
source = v8::Persistent<v8::Object>::New(source_);
|
||||
}
|
||||
|
||||
NodeMap::~NodeMap() {
|
||||
source.Dispose();
|
||||
}
|
||||
|
||||
void NodeMap::processNext() {
|
||||
assert(!queue_.empty());
|
||||
queue_.pop();
|
||||
if (!queue_.empty()) {
|
||||
// When the queue was empty, there was no action in progress, so we can start a new one.
|
||||
NanAsyncQueueWorker(queue_.front().release());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
54
src/node_map.hpp
Normal file
54
src/node_map.hpp
Normal file
@@ -0,0 +1,54 @@
|
||||
#ifndef NODE_MBGL_NODE_MAP
|
||||
#define NODE_MBGL_NODE_MAP
|
||||
|
||||
#include <mbgl/map/map.hpp>
|
||||
#include <mbgl/platform/default/headless_view.hpp>
|
||||
|
||||
#include <node.h>
|
||||
#include <nan.h>
|
||||
|
||||
#include <queue>
|
||||
|
||||
namespace node_mbgl {
|
||||
|
||||
|
||||
class NodeFileSource;
|
||||
|
||||
class NodeMap : public node::ObjectWrap {
|
||||
struct RenderOptions;
|
||||
class RenderWorker;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Static Node Methods
|
||||
public:
|
||||
static void Init(v8::Handle<v8::Object> target);
|
||||
static NAN_METHOD(New);
|
||||
static NAN_METHOD(Load);
|
||||
static NAN_METHOD(Render);
|
||||
|
||||
static std::unique_ptr<NodeMap::RenderOptions> ParseOptions(v8::Local<v8::Object> obj);
|
||||
|
||||
static v8::Persistent<v8::FunctionTemplate> constructorTemplate;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Instance
|
||||
private:
|
||||
NodeMap(v8::Handle<v8::Object> source);
|
||||
~NodeMap();
|
||||
|
||||
void processNext();
|
||||
|
||||
private:
|
||||
v8::Persistent<v8::Object> source;
|
||||
v8::Persistent<v8::Object> self;
|
||||
|
||||
mbgl::HeadlessView view;
|
||||
NodeFileSource &fs;
|
||||
mbgl::Map map;
|
||||
|
||||
std::queue<std::unique_ptr<RenderWorker>> queue_;
|
||||
};
|
||||
|
||||
} // end ns node_mbgl
|
||||
|
||||
#endif
|
||||
59
src/node_map_render_worker.cpp
Normal file
59
src/node_map_render_worker.cpp
Normal file
@@ -0,0 +1,59 @@
|
||||
#include "node_map_render_worker.hpp"
|
||||
#include "node_file_source.hpp"
|
||||
|
||||
#include <mbgl/util/image.hpp>
|
||||
|
||||
namespace node_mbgl {
|
||||
|
||||
NodeMap::RenderWorker::RenderWorker(NodeMap *nodeMap_, std::unique_ptr<RenderOptions> options_,
|
||||
NanCallback *callback_)
|
||||
: NanAsyncWorker(callback_), nodeMap(nodeMap_), options(std::move(options_)) {
|
||||
nodeMap->Ref();
|
||||
}
|
||||
|
||||
NodeMap::RenderWorker::~RenderWorker() {
|
||||
nodeMap->Unref();
|
||||
}
|
||||
|
||||
void NodeMap::RenderWorker::Execute() {
|
||||
fprintf(stderr, "executing render worker\n");
|
||||
try {
|
||||
nodeMap->view.resize(options->width, options->height, options->ratio);
|
||||
nodeMap->map.setAccessToken(options->accessToken);
|
||||
nodeMap->map.resize(options->width, options->height, options->ratio);
|
||||
nodeMap->map.setClasses(options->classes);
|
||||
nodeMap->map.setLonLatZoom(options->longitude, options->latitude, options->zoom);
|
||||
nodeMap->map.setBearing(options->bearing);
|
||||
|
||||
// Run the loop. It will terminate when we don't have any further listeners.
|
||||
nodeMap->map.run();
|
||||
|
||||
fprintf(stderr, "run completed\n");
|
||||
const unsigned int width = options->width * options->ratio;
|
||||
const unsigned int height = options->height * options->ratio;
|
||||
image = mbgl::util::compress_png(width, height, nodeMap->view.readPixels().get());
|
||||
fprintf(stderr, "png compressed\n");
|
||||
} catch (const std::exception &ex) {
|
||||
SetErrorMessage(ex.what());
|
||||
}
|
||||
}
|
||||
|
||||
void NodeMap::RenderWorker::WorkComplete() {
|
||||
NanAsyncWorker::WorkComplete();
|
||||
|
||||
// Continue processing remaining items in the queue.
|
||||
nodeMap->processNext();
|
||||
}
|
||||
|
||||
void NodeMap::RenderWorker::HandleOKCallback() {
|
||||
NanScope();
|
||||
|
||||
v8::Local<v8::Value> argv[] = {
|
||||
NanNull(),
|
||||
NanNewBufferHandle(image.data(), image.length())
|
||||
};
|
||||
|
||||
callback->Call(2, argv);
|
||||
};
|
||||
|
||||
} // ns node_mbgl
|
||||
39
src/node_map_render_worker.hpp
Normal file
39
src/node_map_render_worker.hpp
Normal file
@@ -0,0 +1,39 @@
|
||||
#ifndef NODE_MBGL_NODE_MAP_RENDER_WORKER
|
||||
#define NODE_MBGL_NODE_MAP_RENDER_WORKER
|
||||
|
||||
#include <nan.h>
|
||||
|
||||
#include "node_map.hpp"
|
||||
|
||||
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::string accessToken;
|
||||
std::vector<std::string> classes;
|
||||
};
|
||||
|
||||
class NodeMap::RenderWorker : public NanAsyncWorker {
|
||||
public:
|
||||
RenderWorker(NodeMap *map, std::unique_ptr<RenderOptions> options, NanCallback *callback);
|
||||
~RenderWorker();
|
||||
|
||||
void Execute();
|
||||
void HandleOKCallback();
|
||||
void WorkComplete();
|
||||
|
||||
private:
|
||||
NodeMap *nodeMap;
|
||||
std::unique_ptr<RenderOptions> options;
|
||||
std::string image;
|
||||
};
|
||||
|
||||
} // ns node_mbgl
|
||||
|
||||
#endif // NODE_MBGL_RENDER_WORKER
|
||||
148
src/node_request.cpp
Normal file
148
src/node_request.cpp
Normal file
@@ -0,0 +1,148 @@
|
||||
#include "node_request.hpp"
|
||||
#include "node_file_source.hpp"
|
||||
#include <mbgl/storage/default/request.hpp>
|
||||
#include <mbgl/storage/response.hpp>
|
||||
|
||||
#include <cmath>
|
||||
|
||||
namespace node_mbgl {
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Static Node Methods
|
||||
|
||||
v8::Persistent<v8::FunctionTemplate> NodeRequest::constructorTemplate;
|
||||
|
||||
void NodeRequest::Init(v8::Handle<v8::Object> target) {
|
||||
NanScope();
|
||||
|
||||
v8::Local<v8::FunctionTemplate> t = NanNew<v8::FunctionTemplate>(New);
|
||||
|
||||
t->InstanceTemplate()->SetInternalFieldCount(1);
|
||||
t->SetClassName(NanNew("Request"));
|
||||
|
||||
NODE_SET_PROTOTYPE_METHOD(t, "respond", Respond);
|
||||
|
||||
NanAssignPersistent(constructorTemplate, t);
|
||||
|
||||
target->Set(NanNew("Request"), t->GetFunction());
|
||||
}
|
||||
|
||||
NAN_METHOD(NodeRequest::New) {
|
||||
NanScope();
|
||||
|
||||
// Extract the pointer from the first argument
|
||||
if (args.Length() < 2 || !NanHasInstance(NodeFileSource::constructorTemplate, args[0]) || !args[1]->IsExternal()) {
|
||||
return NanThrowTypeError("Cannot create Request objects explicitly");
|
||||
}
|
||||
|
||||
auto source = v8::Persistent<v8::Object>::New(args[0]->ToObject());
|
||||
auto request = reinterpret_cast<mbgl::Request *>(args[1].As<v8::External>()->Value());
|
||||
auto req = new NodeRequest(source, request);
|
||||
req->Wrap(args.This());
|
||||
|
||||
NanReturnValue(args.This());
|
||||
}
|
||||
|
||||
// TODO: retain the NodeFileSource in a permanent handle!
|
||||
|
||||
v8::Handle<v8::Object> NodeRequest::Create(v8::Handle<v8::Object> source, mbgl::Request *request) {
|
||||
NanScope();
|
||||
v8::Local<v8::Value> argv[] = { v8::Local<v8::Object>::New(source), NanNew<v8::External>(request) };
|
||||
auto instance = constructorTemplate->GetFunction()->NewInstance(2, argv);
|
||||
|
||||
instance->Set(NanNew("url"), NanNew(request->resource.url), v8::ReadOnly);
|
||||
instance->Set(NanNew("kind"), NanNew<v8::Integer>(int(request->resource.kind)), v8::ReadOnly);
|
||||
|
||||
NanReturnValue(instance);
|
||||
}
|
||||
|
||||
NAN_METHOD(NodeRequest::Respond) {
|
||||
NanScope();
|
||||
|
||||
auto nodeRequest = ObjectWrap::Unwrap<NodeRequest>(args.Holder());
|
||||
if (!nodeRequest->request) {
|
||||
return NanThrowError("Request has already been responded to, or was canceled.");
|
||||
}
|
||||
|
||||
auto source = ObjectWrap::Unwrap<NodeFileSource>(nodeRequest->source);
|
||||
|
||||
auto request = nodeRequest->request;
|
||||
nodeRequest->request = nullptr;
|
||||
|
||||
if (args.Length() < 1) {
|
||||
return NanThrowTypeError("First argument must be an error object");
|
||||
} else if (args[0]->BooleanValue()) {
|
||||
auto response = std::make_shared<mbgl::Response>();
|
||||
|
||||
response->status = mbgl::Response::Error;
|
||||
|
||||
// Store the error string.
|
||||
const NanUtf8String message { args[0]->ToString() };
|
||||
response->message = std::string { *message, size_t(message.length()) };
|
||||
|
||||
source->notify(request, response);
|
||||
} else if (args.Length() < 2 || !args[1]->IsObject()) {
|
||||
return NanThrowTypeError("Second argument must be a response object");
|
||||
} else {
|
||||
auto response = std::make_shared<mbgl::Response>();
|
||||
auto res = args[1]->ToObject();
|
||||
|
||||
response->status = mbgl::Response::Successful;
|
||||
|
||||
if (res->Has(NanNew("modified"))) {
|
||||
const double modified = res->Get(NanNew("modified"))->ToNumber()->Value();
|
||||
if (!std::isnan(modified)) {
|
||||
response->modified = modified / 1000; // JS timestamps are milliseconds
|
||||
}
|
||||
}
|
||||
|
||||
if (res->Has(NanNew("expires"))) {
|
||||
const double expires = res->Get(NanNew("expires"))->ToNumber()->Value();
|
||||
if (!std::isnan(expires)) {
|
||||
response->expires = expires / 1000; // JS timestamps are milliseconds
|
||||
}
|
||||
}
|
||||
|
||||
if (res->Has(NanNew("etag"))) {
|
||||
auto etagHandle = res->Get(NanNew("etag"));
|
||||
if (etagHandle->BooleanValue()) {
|
||||
const NanUtf8String etag { etagHandle->ToString() };
|
||||
response->etag = std::string { *etag, size_t(etag.length()) };
|
||||
}
|
||||
}
|
||||
|
||||
if (res->Has(NanNew("data"))) {
|
||||
auto dataHandle = res->Get(NanNew("data"));
|
||||
if (node::Buffer::HasInstance(dataHandle)) {
|
||||
response->data = std::string {
|
||||
node::Buffer::Data(dataHandle),
|
||||
node::Buffer::Length(dataHandle)
|
||||
};
|
||||
} else {
|
||||
return NanThrowTypeError("Response data must be a Buffer");
|
||||
}
|
||||
}
|
||||
|
||||
// Send the response object to the NodeFileSource object
|
||||
source->notify(request, response);
|
||||
}
|
||||
|
||||
NanReturnUndefined();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Instance
|
||||
|
||||
NodeRequest::NodeRequest(v8::Persistent<v8::Object> source_, mbgl::Request *request_)
|
||||
: source(source_), request(request_) {
|
||||
}
|
||||
|
||||
NodeRequest::~NodeRequest() {
|
||||
source.Dispose();
|
||||
}
|
||||
|
||||
void NodeRequest::cancel() {
|
||||
request = nullptr;
|
||||
}
|
||||
|
||||
}
|
||||
36
src/node_request.hpp
Normal file
36
src/node_request.hpp
Normal file
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
|
||||
#include <node.h>
|
||||
#include <nan.h>
|
||||
|
||||
class NodeFileSource;
|
||||
namespace mbgl { class Request; }
|
||||
|
||||
namespace node_mbgl {
|
||||
|
||||
class NodeRequest : public node::ObjectWrap {
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Static Node Methods
|
||||
public:
|
||||
static void Init(v8::Handle<v8::Object> target);
|
||||
static NAN_METHOD(New);
|
||||
static NAN_METHOD(Respond);
|
||||
|
||||
static v8::Handle<v8::Object> Create(v8::Handle<v8::Object> source, mbgl::Request *request);
|
||||
|
||||
static v8::Persistent<v8::FunctionTemplate> constructorTemplate;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Instance
|
||||
public:
|
||||
NodeRequest(v8::Persistent<v8::Object> source, mbgl::Request *request);
|
||||
~NodeRequest();
|
||||
|
||||
void cancel();
|
||||
|
||||
private:
|
||||
v8::Persistent<v8::Object> source;
|
||||
mbgl::Request *request = nullptr;
|
||||
};
|
||||
|
||||
}
|
||||
85
src/thread_object.cpp
Normal file
85
src/thread_object.cpp
Normal file
@@ -0,0 +1,85 @@
|
||||
#include "thread_object.hpp"
|
||||
#include "node_file_source.hpp"
|
||||
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Static Node Methods
|
||||
|
||||
v8::Persistent<v8::FunctionTemplate> ThreadObject::constructorTemplate;
|
||||
|
||||
void ThreadObject::Init(v8::Handle<v8::Object> target) {
|
||||
NanScope();
|
||||
|
||||
v8::Local<v8::FunctionTemplate> t = NanNew<v8::FunctionTemplate>(New);
|
||||
|
||||
t->InstanceTemplate()->SetInternalFieldCount(1);
|
||||
t->SetClassName(NanNew("ThreadObject"));
|
||||
|
||||
NanAssignPersistent(constructorTemplate, t);
|
||||
|
||||
target->Set(NanNew("ThreadObject"), t->GetFunction());
|
||||
}
|
||||
|
||||
NAN_METHOD(ThreadObject::New) {
|
||||
NanScope();
|
||||
|
||||
if (!args.IsConstructCall()) {
|
||||
return NanThrowTypeError("Use the new operator to create new ThreadObject objects");
|
||||
}
|
||||
|
||||
if (args.Length() < 1 || !NanHasInstance(NodeFileSource::constructorTemplate, args[0])) {
|
||||
return NanThrowTypeError("Requires a FileSource as first argument");
|
||||
}
|
||||
|
||||
auto fs = ObjectWrap::Unwrap<NodeFileSource>(args[0]->ToObject());
|
||||
auto to = new ThreadObject(fs);
|
||||
to->Wrap(args.This());
|
||||
|
||||
NanReturnValue(args.This());
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Instance
|
||||
|
||||
ThreadObject::ThreadObject(NodeFileSource *fs) {
|
||||
self = v8::Persistent<v8::Object>::New(handle_);
|
||||
|
||||
auto async = new uv_async_t;
|
||||
async->data = this;
|
||||
uv_async_init(uv_default_loop(), async, [](uv_async_t *as, int) {
|
||||
NanScope();
|
||||
auto to = reinterpret_cast<ThreadObject *>(as->data);
|
||||
|
||||
to->self.Dispose();
|
||||
|
||||
uv_close((uv_handle_t *)as, [](uv_handle_t *handle) {
|
||||
delete reinterpret_cast<uv_async_t *>(handle);
|
||||
});
|
||||
});
|
||||
|
||||
thread = std::thread([this, fs, async]() {
|
||||
auto loop = uv_loop_new();
|
||||
|
||||
fs->request({ mbgl::Resource::Image, "http://example.com" }, loop, [&](const mbgl::Response &res) {
|
||||
fprintf(stderr, "response.status: %d\n", res.status);
|
||||
fprintf(stderr, "response.etag: %s\n", res.etag.c_str());
|
||||
fprintf(stderr, "response.modified: %lld\n", res.modified);
|
||||
fprintf(stderr, "response.expires: %lld\n", res.expires);
|
||||
fprintf(stderr, "response.data.size: %ld\n", res.data.size());
|
||||
fprintf(stderr, "response.message: %s\n", res.message.c_str());
|
||||
});
|
||||
|
||||
uv_run(loop, UV_RUN_DEFAULT);
|
||||
|
||||
uv_loop_delete(loop);
|
||||
|
||||
// Notify that the loop has finished executing.
|
||||
uv_async_send(async);
|
||||
});
|
||||
}
|
||||
|
||||
ThreadObject::~ThreadObject() {
|
||||
thread.join();
|
||||
}
|
||||
28
src/thread_object.hpp
Normal file
28
src/thread_object.hpp
Normal file
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include <node.h>
|
||||
#include <nan.h>
|
||||
|
||||
#include <thread>
|
||||
|
||||
class NodeFileSource;
|
||||
|
||||
class ThreadObject : public node::ObjectWrap {
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Static Node Methods
|
||||
public:
|
||||
static void Init(v8::Handle<v8::Object> target);
|
||||
static NAN_METHOD(New);
|
||||
|
||||
static v8::Persistent<v8::FunctionTemplate> constructorTemplate;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Instance
|
||||
private:
|
||||
ThreadObject(NodeFileSource *fs);
|
||||
~ThreadObject();
|
||||
|
||||
private:
|
||||
std::thread thread;
|
||||
v8::Persistent<v8::Object> self;
|
||||
};
|
||||
19
test/file_source.test.js
Normal file
19
test/file_source.test.js
Normal file
@@ -0,0 +1,19 @@
|
||||
/* jshint node: true, unused: false */
|
||||
/* global describe, it */
|
||||
'use strict';
|
||||
var assert = require('assert');
|
||||
|
||||
var mbgl = require('..');
|
||||
|
||||
describe('FileSource', function() {
|
||||
|
||||
describe('constructor', function() {
|
||||
it('must be called with new', function() {
|
||||
assert.throws(function() {
|
||||
mbgl.FileSource();
|
||||
}, /Use the new operator to create new FileSource objects/);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
134
test/map.test.js
Normal file
134
test/map.test.js
Normal file
@@ -0,0 +1,134 @@
|
||||
/* jshint node: true, unused: false */
|
||||
/* global describe, it, beforeEach, afterEach */
|
||||
'use strict';
|
||||
var assert = require('assert');
|
||||
|
||||
var mbgl = require('..');
|
||||
|
||||
describe('Map', function() {
|
||||
|
||||
describe('constructor', function() {
|
||||
it('must be called with new', function() {
|
||||
assert.throws(function() {
|
||||
mbgl.Map();
|
||||
}, /Use the new operator to create new Map objects/);
|
||||
});
|
||||
|
||||
it('should require a FileSource object as first parameter', function() {
|
||||
assert.throws(function() {
|
||||
new mbgl.Map();
|
||||
}, /Requires a FileSource as first argument/);
|
||||
|
||||
assert.throws(function() {
|
||||
new mbgl.Map('fileSource');
|
||||
}, /Requires a FileSource as first argument/);
|
||||
|
||||
assert.throws(function() {
|
||||
new mbgl.Map({});
|
||||
}, /Requires a FileSource as first argument/);
|
||||
});
|
||||
|
||||
it('should require the FileSource object to have request and cancel methods', function() {
|
||||
var fs = new mbgl.FileSource();
|
||||
|
||||
assert.throws(function() {
|
||||
new mbgl.Map(fs);
|
||||
}, /FileSource must have a request member function/);
|
||||
|
||||
fs.request = 'test';
|
||||
assert.throws(function() {
|
||||
new mbgl.Map(fs);
|
||||
}, /FileSource must have a request member function/);
|
||||
|
||||
fs.request = function() {};
|
||||
assert.throws(function() {
|
||||
new mbgl.Map(fs);
|
||||
}, /FileSource must have a cancel member function/);
|
||||
|
||||
fs.cancel = 'test';
|
||||
assert.throws(function() {
|
||||
new mbgl.Map(fs);
|
||||
}, /FileSource must have a cancel member function/);
|
||||
|
||||
fs.cancel = function() {};
|
||||
assert.doesNotThrow(function() {
|
||||
new mbgl.Map(fs);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('load styles', function() {
|
||||
var map;
|
||||
|
||||
beforeEach(function() {
|
||||
var fs = new mbgl.FileSource();
|
||||
fs.request = function() {};
|
||||
fs.cancel = function() {};
|
||||
map = new mbgl.Map(fs);
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
map = null;
|
||||
});
|
||||
|
||||
it('requires a string or object as the first parameter', function() {
|
||||
assert.throws(function() {
|
||||
map.load();
|
||||
}, /Requires a map style as first argument/);
|
||||
|
||||
assert.throws(function() {
|
||||
map.load('invalid');
|
||||
}, /Expect either an object or array at root/);
|
||||
});
|
||||
|
||||
it('accepts a stylesheet string', function() {
|
||||
map.load('{}');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
describe('render arguments', function() {
|
||||
var map;
|
||||
|
||||
beforeEach(function() {
|
||||
var fs = new mbgl.FileSource();
|
||||
fs.request = function() {};
|
||||
fs.cancel = function() {};
|
||||
map = new mbgl.Map(fs);
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
map = null;
|
||||
});
|
||||
|
||||
it('requires a string or object as the first parameter', function() {
|
||||
assert.throws(function() {
|
||||
map.render();
|
||||
}, /First argument must be an options object/);
|
||||
|
||||
assert.throws(function() {
|
||||
map.render('invalid');
|
||||
}, /First argument must be an options object/);
|
||||
});
|
||||
|
||||
it('requires a callback as the second parameter', function() {
|
||||
assert.throws(function() {
|
||||
map.render({});
|
||||
}, /Second argument must be a callback function/);
|
||||
|
||||
assert.throws(function() {
|
||||
map.render({}, 'invalid');
|
||||
}, /Second argument must be a callback function/);
|
||||
});
|
||||
|
||||
it('requires a callback as the second parameter', function(done) {
|
||||
map.render({}, function(err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
1
vendor/mbgl
vendored
Submodule
1
vendor/mbgl
vendored
Submodule
Submodule vendor/mbgl added at da981e7825
Reference in New Issue
Block a user