Make Node Map object options "request" property optional (#904)
This commit is contained in:
@@ -30,6 +30,38 @@ npm run test-suite
|
||||
|
||||
## Rendering a map tile
|
||||
|
||||
The minimal example requires only the instantiation of the `mbgl.Map` object, loading a style and calling the `map.render` method:
|
||||
|
||||
```js
|
||||
var mbgl = require('@maplibre/maplibre-gl-native');
|
||||
var sharp = require('sharp');
|
||||
|
||||
var map = new mbgl.Map();
|
||||
|
||||
map.load(require('./test/fixtures/style.json'));
|
||||
|
||||
map.render(function(err, buffer) {
|
||||
if (err) throw err;
|
||||
|
||||
map.release();
|
||||
|
||||
var image = sharp(buffer, {
|
||||
raw: {
|
||||
width: 512,
|
||||
height: 512,
|
||||
channels: 4
|
||||
}
|
||||
});
|
||||
|
||||
// Convert raw image buffer to PNG
|
||||
image.toFile('image.png', function(err) {
|
||||
if (err) throw err;
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
But you can customize the map providing an options object to `mbgl.Map` constructor and to `map.render` method:
|
||||
|
||||
```js
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
@@ -87,7 +119,7 @@ When you are finished using a map object, you can call `map.release()` to perman
|
||||
|
||||
## Implementing a file source
|
||||
|
||||
When creating a `Map`, you must pass an options object (with a required `request` method and optional 'ratio' number) as the first parameter.
|
||||
When creating a `Map`, you can optionally pass an options object (with an optional `request` method and optional `ratio` number) as the first parameter. The `request()` method handles a request for a resource. The `ratio` sets the scale at which the map will render tiles, such as `2.0` for rendering images for high pixel density displays:
|
||||
|
||||
```js
|
||||
var map = new mbgl.Map({
|
||||
@@ -98,7 +130,7 @@ var map = new mbgl.Map({
|
||||
});
|
||||
```
|
||||
|
||||
The `request()` method handles a request for a resource. The `ratio` sets the scale at which the map will render tiles, such as `2.0` for rendering images for high pixel density displays. The `req` parameter has two properties:
|
||||
If you omit the `request` method, the `map` object will use the default internal request handlers, which is ok for most cases. However, if you have specific needs, you can implement your own `request` handler. When a `request` method is provided, all `map` resources will be requested by calling the `request` method with two parameters, called `req` and `callback` respectively in this example. The `req` parameter has two properties:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -123,7 +155,18 @@ The `kind` is an enum and defined in [`mbgl.Resource`](https://github.com/maplib
|
||||
|
||||
The `kind` enum has no significance for anything but serves as a hint to your implemention as to what sort of resource to expect. E.g., your implementation could choose caching strategies based on the expected file type.
|
||||
|
||||
The `request` implementation should pass uncompressed data to `callback`. If you are downloading assets from a source that applies gzip transport encoding, the implementation must decompress the results before passing them on.
|
||||
The `callback` parameter is a function that must be called with two parameters: an error message (if there are no errors, then you must pass `null`), and a response object:
|
||||
|
||||
```js
|
||||
{
|
||||
data: {data}, // required, must be a byte array, usually a Buffer object
|
||||
modified: {modified}, // Date, optional
|
||||
expires: {expires}, // Date, optional
|
||||
etag: {etag} // string, optional
|
||||
}
|
||||
```
|
||||
|
||||
If there is no data to be sent to the `callback` (empty data, or `no-content` respose), then it must be called without parameters. The `request` implementation should pass uncompressed data to `callback`. If you are downloading assets from a source that applies gzip transport encoding, the implementation must decompress the results before passing them on.
|
||||
|
||||
A sample implementation that reads files from disk would look like the following:
|
||||
|
||||
|
||||
4
platform/node/index.d.ts
vendored
4
platform/node/index.d.ts
vendored
@@ -35,7 +35,7 @@ declare module '@maplibre/maplibre-gl-native' {
|
||||
/**
|
||||
* Will be used during a `Map.render` call to request all necessary map resources (tiles, fonts...)
|
||||
*/
|
||||
request: (
|
||||
request?: (
|
||||
request: { url: string; kind: ResourceKind },
|
||||
callback: (error?: Error, response?: RequestResponse) => void,
|
||||
) => void;
|
||||
@@ -111,7 +111,7 @@ declare module '@maplibre/maplibre-gl-native' {
|
||||
* A `Map` instance is used to render images from map views
|
||||
*/
|
||||
class Map {
|
||||
constructor(mapOptions: MapOptions);
|
||||
constructor(mapOptions?: MapOptions);
|
||||
|
||||
/**
|
||||
* Load a style into a map
|
||||
|
||||
@@ -6,43 +6,49 @@ var mbgl = require('../../lib/node-v' + process.versions.modules + '/mbgl');
|
||||
var constructor = mbgl.Map.prototype.constructor;
|
||||
|
||||
var Map = function(options) {
|
||||
if (!(options instanceof Object)) {
|
||||
if (options && !(options instanceof Object)) {
|
||||
throw TypeError("Requires an options object as first argument");
|
||||
}
|
||||
|
||||
if (!options.hasOwnProperty('request') || !(options.request instanceof Function)) {
|
||||
throw TypeError("Options object must have a 'request' method");
|
||||
if (options && options.hasOwnProperty('request') && !(options.request instanceof Function)) {
|
||||
throw TypeError("Options object 'request' property must be a function");
|
||||
}
|
||||
|
||||
var request = options.request;
|
||||
if (options && options.request) {
|
||||
var request = options.request;
|
||||
|
||||
return new constructor(Object.assign(options, {
|
||||
request: function(req) {
|
||||
// Protect against `request` implementations that call the callback synchronously,
|
||||
// call it multiple times, or throw exceptions.
|
||||
// http://blog.izs.me/post/59142742143/designing-apis-for-asynchrony
|
||||
return new constructor(Object.assign(options, {
|
||||
request: function(req) {
|
||||
// Protect against `request` implementations that call the callback synchronously,
|
||||
// call it multiple times, or throw exceptions.
|
||||
// http://blog.izs.me/post/59142742143/designing-apis-for-asynchrony
|
||||
|
||||
var responded = false;
|
||||
var callback = function() {
|
||||
var args = arguments;
|
||||
if (!responded) {
|
||||
responded = true;
|
||||
process.nextTick(function() {
|
||||
req.respond.apply(req, args);
|
||||
});
|
||||
} else {
|
||||
console.warn('request function responded multiple times; it should call the callback only once');
|
||||
var responded = false;
|
||||
var callback = function() {
|
||||
var args = arguments;
|
||||
if (!responded) {
|
||||
responded = true;
|
||||
process.nextTick(function() {
|
||||
req.respond.apply(req, args);
|
||||
});
|
||||
} else {
|
||||
console.warn('request function responded multiple times; it should call the callback only once');
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
request(req, callback);
|
||||
} catch (e) {
|
||||
console.warn('request function threw an exception; it should call the callback with an error instead');
|
||||
callback(e);
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
request(req, callback);
|
||||
} catch (e) {
|
||||
console.warn('request function threw an exception; it should call the callback with an error instead');
|
||||
callback(e);
|
||||
}
|
||||
}
|
||||
}));
|
||||
}));
|
||||
} else if (options) {
|
||||
return new constructor(options);
|
||||
} else {
|
||||
return new constructor();
|
||||
}
|
||||
};
|
||||
|
||||
Map.prototype = mbgl.Map.prototype;
|
||||
|
||||
@@ -171,17 +171,23 @@ void NodeMap::New(const Nan::FunctionCallbackInfo<v8::Value>& info) {
|
||||
return Nan::ThrowTypeError("Use the new operator to create new Map objects");
|
||||
}
|
||||
|
||||
if (info.Length() < 1 || !info[0]->IsObject()) {
|
||||
if (info.Length() > 0 && !info[0]->IsObject()) {
|
||||
return Nan::ThrowTypeError("Requires an options object as first argument");
|
||||
}
|
||||
|
||||
auto options = Nan::To<v8::Object>(info[0]).ToLocalChecked();
|
||||
v8::Local<v8::Object> options;
|
||||
|
||||
// Check that 'request' is set. If 'cancel' is set it must be a
|
||||
if(info.Length() > 0) {
|
||||
options = Nan::To<v8::Object>(info[0]).ToLocalChecked();
|
||||
} else {
|
||||
options = Nan::New<v8::Object>();
|
||||
}
|
||||
|
||||
// Check that if 'request' is set it must be a function, if 'cancel' is set it must be a
|
||||
// function and if 'ratio' is set it must be a number.
|
||||
if (!Nan::Has(options, Nan::New("request").ToLocalChecked()).FromJust()
|
||||
|| !Nan::Get(options, Nan::New("request").ToLocalChecked()).ToLocalChecked()->IsFunction()) {
|
||||
return Nan::ThrowError("Options object must have a 'request' method");
|
||||
if (Nan::Has(options, Nan::New("request").ToLocalChecked()).FromJust()
|
||||
&& !Nan::Get(options, Nan::New("request").ToLocalChecked()).ToLocalChecked()->IsFunction()) {
|
||||
return Nan::ThrowError("Options object 'request' property must be a function");
|
||||
}
|
||||
|
||||
if (Nan::Has(options, Nan::New("cancel").ToLocalChecked()).FromJust()
|
||||
@@ -196,11 +202,14 @@ void NodeMap::New(const Nan::FunctionCallbackInfo<v8::Value>& info) {
|
||||
|
||||
info.This()->SetInternalField(1, options);
|
||||
|
||||
mbgl::FileSourceManager::get()->registerFileSourceFactory(
|
||||
mbgl::FileSourceType::ResourceLoader, [](const mbgl::ResourceOptions& resourceOptions, const mbgl::ClientOptions&) {
|
||||
return std::make_unique<node_mbgl::NodeFileSource>(
|
||||
reinterpret_cast<node_mbgl::NodeMap*>(resourceOptions.platformContext()));
|
||||
});
|
||||
if(Nan::Has(options, Nan::New("request").ToLocalChecked()).FromJust()
|
||||
&& Nan::Get(options, Nan::New("request").ToLocalChecked()).ToLocalChecked()->IsFunction()) {
|
||||
mbgl::FileSourceManager::get()->registerFileSourceFactory(
|
||||
mbgl::FileSourceType::ResourceLoader, [](const mbgl::ResourceOptions& resourceOptions, const mbgl::ClientOptions&) {
|
||||
return std::make_unique<node_mbgl::NodeFileSource>(
|
||||
reinterpret_cast<node_mbgl::NodeMap*>(resourceOptions.platformContext()));
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
auto nodeMap = new NodeMap(options);
|
||||
|
||||
@@ -17,10 +17,11 @@ test('Map', function(t) {
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.test('must be constructed with options object', function(t) {
|
||||
t.throws(function() {
|
||||
new mbgl.Map();
|
||||
}, /Requires an options object as first argument/);
|
||||
t.test('must be constructed with no options or with options object', function(t) {
|
||||
t.doesNotThrow(function() {
|
||||
var map = new mbgl.Map();
|
||||
map.release();
|
||||
});
|
||||
|
||||
t.throws(function() {
|
||||
new mbgl.Map('options');
|
||||
@@ -29,17 +30,18 @@ test('Map', function(t) {
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.test('requires request property', function(t) {
|
||||
t.test('requires request property to be a function', function(t) {
|
||||
var options = {};
|
||||
|
||||
t.throws(function() {
|
||||
new mbgl.Map(options);
|
||||
}, /Options object must have a 'request' method/);
|
||||
t.doesNotThrow(function() {
|
||||
var map = new mbgl.Map(options);
|
||||
map.release();
|
||||
});
|
||||
|
||||
options.request = 'test';
|
||||
t.throws(function() {
|
||||
new mbgl.Map(options);
|
||||
}, /Options object must have a 'request' method/);
|
||||
}, /Options object 'request' property must be a function/);
|
||||
|
||||
options.request = function() {};
|
||||
t.doesNotThrow(function() {
|
||||
@@ -51,9 +53,7 @@ test('Map', function(t) {
|
||||
});
|
||||
|
||||
t.test('optional cancel property must be a function', function(t) {
|
||||
var options = {
|
||||
request: function() {}
|
||||
};
|
||||
var options = {};
|
||||
|
||||
options.cancel = 'test';
|
||||
t.throws(function() {
|
||||
@@ -71,9 +71,7 @@ test('Map', function(t) {
|
||||
|
||||
|
||||
t.test('optional ratio property must be a number', function(t) {
|
||||
var options = {
|
||||
request: function() {}
|
||||
};
|
||||
var options = {};
|
||||
|
||||
options.ratio = 'test';
|
||||
t.throws(function() {
|
||||
|
||||
Reference in New Issue
Block a user