summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKonstantin Käfer <mail@kkaefer.com>2015-02-04 18:21:39 +0100
committerKonstantin Käfer <mail@kkaefer.com>2015-02-04 18:21:39 +0100
commit07867edd4764e0801f715bff2de899ae1620eb8c (patch)
tree6615f572457ff03ffed8758c856c7a658435d57a
downloadqtlocation-mapboxgl-07867edd4764e0801f715bff2de899ae1620eb8c.tar.gz
initial version with updated build system
-rw-r--r--.clang-format18
-rw-r--r--.gitignore3
-rw-r--r--.gitmodules3
-rw-r--r--Makefile43
-rw-r--r--binding.gyp59
-rw-r--r--package.json16
-rw-r--r--src/mbgl.cpp26
-rw-r--r--src/node_file_source.cpp156
-rw-r--r--src/node_file_source.hpp49
-rw-r--r--src/node_map.cpp185
-rw-r--r--src/node_map.hpp54
-rw-r--r--src/node_map_render_worker.cpp59
-rw-r--r--src/node_map_render_worker.hpp39
-rw-r--r--src/node_request.cpp148
-rw-r--r--src/node_request.hpp36
-rw-r--r--src/thread_object.cpp85
-rw-r--r--src/thread_object.hpp28
-rw-r--r--test/file_source.test.js19
-rw-r--r--test/map.test.js134
m---------vendor/mbgl6
20 files changed, 1166 insertions, 0 deletions
diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000000..86d562e74e
--- /dev/null
+++ b/.clang-format
@@ -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
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000..b8de80b584
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+/build
+/lib
+/node_modules
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000000..863c7f66f4
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "vendor/mbgl"]
+ path = vendor/mbgl
+ url = https://github.com/mapbox/mapbox-gl-native.git
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000000..ef65d4cf4a
--- /dev/null
+++ b/Makefile
@@ -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
diff --git a/binding.gyp b/binding.gyp
new file mode 100644
index 0000000000..0d4922fad5
--- /dev/null
+++ b/binding.gyp
@@ -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'
+ }
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/package.json b/package.json
new file mode 100644
index 0000000000..c93a4cb8a0
--- /dev/null
+++ b/package.json
@@ -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
+}
diff --git a/src/mbgl.cpp b/src/mbgl.cpp
new file mode 100644
index 0000000000..677e72b2d1
--- /dev/null
+++ b/src/mbgl.cpp
@@ -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)
diff --git a/src/node_file_source.cpp b/src/node_file_source.cpp
new file mode 100644
index 0000000000..6ce81c0647
--- /dev/null
+++ b/src/node_file_source.cpp
@@ -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);
+}
+
+}
diff --git a/src/node_file_source.hpp b/src/node_file_source.hpp
new file mode 100644
index 0000000000..293dcf63ce
--- /dev/null
+++ b/src/node_file_source.hpp
@@ -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;
+};
+
+}
diff --git a/src/node_map.cpp b/src/node_map.cpp
new file mode 100644
index 0000000000..24672af66a
--- /dev/null
+++ b/src/node_map.cpp
@@ -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());
+ }
+}
+
+}
diff --git a/src/node_map.hpp b/src/node_map.hpp
new file mode 100644
index 0000000000..fdc625c038
--- /dev/null
+++ b/src/node_map.hpp
@@ -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
diff --git a/src/node_map_render_worker.cpp b/src/node_map_render_worker.cpp
new file mode 100644
index 0000000000..d11b5a0097
--- /dev/null
+++ b/src/node_map_render_worker.cpp
@@ -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
diff --git a/src/node_map_render_worker.hpp b/src/node_map_render_worker.hpp
new file mode 100644
index 0000000000..920332f7d9
--- /dev/null
+++ b/src/node_map_render_worker.hpp
@@ -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
diff --git a/src/node_request.cpp b/src/node_request.cpp
new file mode 100644
index 0000000000..9d5b543880
--- /dev/null
+++ b/src/node_request.cpp
@@ -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;
+}
+
+}
diff --git a/src/node_request.hpp b/src/node_request.hpp
new file mode 100644
index 0000000000..d07e93d42d
--- /dev/null
+++ b/src/node_request.hpp
@@ -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;
+};
+
+}
diff --git a/src/thread_object.cpp b/src/thread_object.cpp
new file mode 100644
index 0000000000..ea2e83130d
--- /dev/null
+++ b/src/thread_object.cpp
@@ -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();
+}
diff --git a/src/thread_object.hpp b/src/thread_object.hpp
new file mode 100644
index 0000000000..3fbdb0400e
--- /dev/null
+++ b/src/thread_object.hpp
@@ -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;
+};
diff --git a/test/file_source.test.js b/test/file_source.test.js
new file mode 100644
index 0000000000..1d7d86fe9a
--- /dev/null
+++ b/test/file_source.test.js
@@ -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/);
+ });
+
+ });
+
+});
diff --git a/test/map.test.js b/test/map.test.js
new file mode 100644
index 0000000000..e719e789e5
--- /dev/null
+++ b/test/map.test.js
@@ -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);
+ });
+ });
+
+ });
+
+});
diff --git a/vendor/mbgl b/vendor/mbgl
new file mode 160000
+Subproject da981e78253ab2b8715f4e10e175be4bea43c13