Upgraded several dependencies, resolved #30
12
.babelrc
@ -1,5 +1,11 @@
|
||||
{
|
||||
"presets": [
|
||||
"es2015"
|
||||
]
|
||||
"presets": ["@babel/preset-env"],
|
||||
"plugins": [
|
||||
[
|
||||
"@babel/plugin-transform-runtime",
|
||||
{
|
||||
"regenerator": true
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
{
|
||||
"extends": ["airbnb"],
|
||||
"extends": ["airbnb-base"],
|
||||
"env": {
|
||||
"es6": true,
|
||||
"node": true,
|
||||
|
||||
22
README.md
@ -209,6 +209,12 @@ http://localhost:8080/render?height=1024&width=1024¢er=-80,-20&zoom=3&style=
|
||||
|
||||
If your style JSON points to local tilesets, you must have started the server up using those local tilesets.
|
||||
|
||||
To test the server from the command line, for example with the excellent tool [`httpie`](https://httpie.org/) with the style file `tests/fixtures/example-style-mbtiles-source.json`:
|
||||
|
||||
```
|
||||
http :8080/render width=400 height=400 zoom=0 center=0,0 style:=@tests/fixtures/example-style.json > /tmp/test.png
|
||||
```
|
||||
|
||||
##### POST requests:
|
||||
|
||||
You can do a POST request with all of the above parameters in the body of the request, and the style can be passed directly as JSON instead of URL encoded.
|
||||
@ -231,28 +237,30 @@ This uses the `pixelmatch` package to determine if output images match those tha
|
||||
|
||||
## Docker
|
||||
|
||||
Pull the latest container from [Docker Hub](https://hub.docker.com/r/consbio/mbgl-renderer):
|
||||
Pull the latest image from [Docker Hub](https://hub.docker.com/r/consbio/mbgl-renderer):
|
||||
|
||||
```
|
||||
docker pull consbio/mbgl-renderer:latest
|
||||
```
|
||||
|
||||
Or build your own docker container:
|
||||
To run `mbgl-server` in the docker container on port 8080:
|
||||
|
||||
```
|
||||
docker build -t mbgl-server -f docker/Dockerfile .
|
||||
docker run --rm -p 8080:80 consbio/mbgl-renderer
|
||||
```
|
||||
|
||||
To run the docker container on port 8080:
|
||||
Mount your local tiles directory to `/app/tiles` in the container to use these in the server or CLI:
|
||||
|
||||
```
|
||||
docker run --rm -p 8080:80 mbgl-server
|
||||
docker run --rm -p 8080:80 -v $(pwd)tests/fixtures:/app/tiles consbio/mbgl-renderer
|
||||
```
|
||||
|
||||
Mount your local tiles, if you want to use with your docker container:
|
||||
### Build your own image:
|
||||
|
||||
Build your own docker container with name `<image_name>`:
|
||||
|
||||
```
|
||||
docker run --rm -p 8080:80 -v$(pwd)/tests/fixtures:/app/tiles mbgl-server
|
||||
docker build -t <image_name> -f docker/Dockerfile .
|
||||
```
|
||||
|
||||
## Headless servers
|
||||
|
||||
198
dist/cli.js
vendored
@ -1,171 +1,169 @@
|
||||
#!/usr/bin/env node
|
||||
'use strict';
|
||||
"use strict";
|
||||
|
||||
var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();
|
||||
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
||||
|
||||
var _fs = require('fs');
|
||||
var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
|
||||
|
||||
var _fs2 = _interopRequireDefault(_fs);
|
||||
var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
|
||||
|
||||
var _commander = require('commander');
|
||||
var _fs = _interopRequireDefault(require("fs"));
|
||||
|
||||
var _commander2 = _interopRequireDefault(_commander);
|
||||
var _commander = _interopRequireDefault(require("commander"));
|
||||
|
||||
var _request = require('request');
|
||||
var _request = _interopRequireDefault(require("request"));
|
||||
|
||||
var _request2 = _interopRequireDefault(_request);
|
||||
var _package = require("../package.json");
|
||||
|
||||
var _package = require('../package.json');
|
||||
|
||||
var _render = require('./render');
|
||||
|
||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||
|
||||
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
|
||||
var _render = require("./render");
|
||||
|
||||
var raiseError = function raiseError(msg) {
|
||||
console.error('ERROR:', msg);
|
||||
process.exit(1);
|
||||
console.error('ERROR:', msg);
|
||||
process.exit(1);
|
||||
};
|
||||
|
||||
var parseListToFloat = function parseListToFloat(text) {
|
||||
return text.split(',').map(Number);
|
||||
return text.split(',').map(Number);
|
||||
};
|
||||
|
||||
_commander2.default.version(_package.version).description('Export a Mapbox GL map to image. You must provide either center and zoom, or bounds.').arguments('<style.json> <img_filename> <width> <height>').option('-c, --center <longitude,latitude>', 'center of map (NO SPACES)', parseListToFloat).option('-z, --zoom <n>', 'Zoom level', parseFloat).option('-r, --ratio <n>', 'Pixel ratio', parseInt).option('-b, --bounds <west,south,east,north>', 'Bounds (NO SPACES)', parseListToFloat).option('-t, --tiles <mbtiles_path>', 'Directory containing local mbtiles files to render').option('--token <mapbox access token>', 'Mapbox access token (required for using Mapbox styles and sources)').parse(process.argv);
|
||||
_commander["default"].version(_package.version).description('Export a Mapbox GL map to image. You must provide either center and zoom, or bounds.').arguments('<style.json> <img_filename> <width> <height>').option('-c, --center <longitude,latitude>', 'center of map (NO SPACES)', parseListToFloat).option('-z, --zoom <n>', 'Zoom level', parseFloat).option('-r, --ratio <n>', 'Pixel ratio', parseInt).option('-b, --bounds <west,south,east,north>', 'Bounds (NO SPACES)', parseListToFloat).option('-t, --tiles <mbtiles_path>', 'Directory containing local mbtiles files to render').option('--token <mapbox access token>', 'Mapbox access token (required for using Mapbox styles and sources)').parse(process.argv);
|
||||
|
||||
var _cli$args = _slicedToArray(_commander2.default.args, 4),
|
||||
var _cli$args = (0, _slicedToArray2["default"])(_commander["default"].args, 4),
|
||||
styleFilename = _cli$args[0],
|
||||
imgFilename = _cli$args[1],
|
||||
width = _cli$args[2],
|
||||
height = _cli$args[3],
|
||||
_cli$center = _commander2.default.center,
|
||||
center = _cli$center === undefined ? null : _cli$center,
|
||||
_cli$zoom = _commander2.default.zoom,
|
||||
zoom = _cli$zoom === undefined ? null : _cli$zoom,
|
||||
_cli$ratio = _commander2.default.ratio,
|
||||
ratio = _cli$ratio === undefined ? 1 : _cli$ratio,
|
||||
_cli$bounds = _commander2.default.bounds,
|
||||
bounds = _cli$bounds === undefined ? null : _cli$bounds,
|
||||
_cli$tiles = _commander2.default.tiles,
|
||||
tilePath = _cli$tiles === undefined ? null : _cli$tiles,
|
||||
_cli$token = _commander2.default.token,
|
||||
token = _cli$token === undefined ? null : _cli$token;
|
||||
|
||||
// verify that all arguments are present
|
||||
_cli$center = _commander["default"].center,
|
||||
center = _cli$center === void 0 ? null : _cli$center,
|
||||
_cli$zoom = _commander["default"].zoom,
|
||||
zoom = _cli$zoom === void 0 ? null : _cli$zoom,
|
||||
_cli$ratio = _commander["default"].ratio,
|
||||
ratio = _cli$ratio === void 0 ? 1 : _cli$ratio,
|
||||
_cli$bounds = _commander["default"].bounds,
|
||||
bounds = _cli$bounds === void 0 ? null : _cli$bounds,
|
||||
_cli$tiles = _commander["default"].tiles,
|
||||
tilePath = _cli$tiles === void 0 ? null : _cli$tiles,
|
||||
_cli$token = _commander["default"].token,
|
||||
token = _cli$token === void 0 ? null : _cli$token; // verify that all arguments are present
|
||||
|
||||
|
||||
if (!styleFilename) {
|
||||
raiseError('style is a required parameter');
|
||||
raiseError('style is a required parameter');
|
||||
}
|
||||
|
||||
if (!imgFilename) {
|
||||
raiseError('output image filename is a required parameter');
|
||||
raiseError('output image filename is a required parameter');
|
||||
}
|
||||
|
||||
if (!width) {
|
||||
raiseError('width is a required parameter');
|
||||
raiseError('width is a required parameter');
|
||||
}
|
||||
|
||||
if (!height) {
|
||||
raiseError('height is a required parameter');
|
||||
raiseError('height is a required parameter');
|
||||
}
|
||||
|
||||
var imgWidth = parseInt(width, 10);
|
||||
var imgHeight = parseInt(height, 10);
|
||||
|
||||
var isMapboxStyle = (0, _render.isMapboxStyleURL)(styleFilename);
|
||||
|
||||
if (!(isMapboxStyle || _fs2.default.existsSync(styleFilename))) {
|
||||
raiseError('Style JSON file does not exist: ' + styleFilename);
|
||||
if (!(isMapboxStyle || _fs["default"].existsSync(styleFilename))) {
|
||||
raiseError("Style JSON file does not exist: ".concat(styleFilename));
|
||||
}
|
||||
|
||||
if (imgWidth <= 0 || imgHeight <= 0) {
|
||||
raiseError('Width and height must be greater than 0, they are width:' + imgWidth + ' height:' + imgHeight);
|
||||
raiseError("Width and height must be greater than 0, they are width:".concat(imgWidth, " height:").concat(imgHeight));
|
||||
}
|
||||
|
||||
if (center !== null) {
|
||||
if (center.length !== 2) {
|
||||
raiseError('Center must be longitude,latitude. Invalid value found: ' + [].concat(_toConsumableArray(center)));
|
||||
}
|
||||
if (center.length !== 2) {
|
||||
raiseError("Center must be longitude,latitude. Invalid value found: ".concat((0, _toConsumableArray2["default"])(center)));
|
||||
}
|
||||
|
||||
if (Math.abs(center[0]) > 180) {
|
||||
raiseError('Center longitude is outside world bounds (-180 to 180 deg): ' + center[0]);
|
||||
}
|
||||
if (Math.abs(center[0]) > 180) {
|
||||
raiseError("Center longitude is outside world bounds (-180 to 180 deg): ".concat(center[0]));
|
||||
}
|
||||
|
||||
if (Math.abs(center[1]) > 90) {
|
||||
raiseError('Center latitude is outside world bounds (-90 to 90 deg): ' + center[1]);
|
||||
}
|
||||
if (Math.abs(center[1]) > 90) {
|
||||
raiseError("Center latitude is outside world bounds (-90 to 90 deg): ".concat(center[1]));
|
||||
}
|
||||
}
|
||||
|
||||
if (zoom !== null && (zoom < 0 || zoom > 22)) {
|
||||
raiseError('Zoom level is outside supported range (0-22): ' + zoom);
|
||||
raiseError("Zoom level is outside supported range (0-22): ".concat(zoom));
|
||||
}
|
||||
|
||||
if (bounds !== null) {
|
||||
if (bounds.length !== 4) {
|
||||
raiseError('Bounds must be west,south,east,north. Invalid value found: ' + [].concat(_toConsumableArray(bounds)));
|
||||
}
|
||||
if (bounds.length !== 4) {
|
||||
raiseError("Bounds must be west,south,east,north. Invalid value found: ".concat((0, _toConsumableArray2["default"])(bounds)));
|
||||
}
|
||||
}
|
||||
|
||||
if (tilePath !== null) {
|
||||
if (!_fs2.default.existsSync(tilePath)) {
|
||||
raiseError('Path to mbtiles files does not exist: ' + tilePath);
|
||||
}
|
||||
if (!_fs["default"].existsSync(tilePath)) {
|
||||
raiseError("Path to mbtiles files does not exist: ".concat(tilePath));
|
||||
}
|
||||
}
|
||||
|
||||
console.log('\n\n-------- Export Mapbox GL Map --------');
|
||||
console.log('style: %j', styleFilename);
|
||||
console.log('output image: ' + imgFilename + ' (' + width + 'w x ' + height + 'h)');
|
||||
console.log("output image: ".concat(imgFilename, " (").concat(width, "w x ").concat(height, "h)"));
|
||||
|
||||
if (tilePath !== null) {
|
||||
console.log('using local mbtiles in: ' + tilePath);
|
||||
console.log("using local mbtiles in: ".concat(tilePath));
|
||||
}
|
||||
|
||||
var renderRequest = function renderRequest(style) {
|
||||
(0, _render.render)(style, imgWidth, imgHeight, {
|
||||
zoom: zoom,
|
||||
ratio: ratio,
|
||||
center: center,
|
||||
bounds: bounds,
|
||||
tilePath: tilePath,
|
||||
token: token
|
||||
}).then(function (data) {
|
||||
_fs2.default.writeFileSync(imgFilename, data);
|
||||
console.log('Done!');
|
||||
console.log('\n');
|
||||
}).catch(function (err) {
|
||||
console.error(err);
|
||||
});
|
||||
(0, _render.render)(style, imgWidth, imgHeight, {
|
||||
zoom: zoom,
|
||||
ratio: ratio,
|
||||
center: center,
|
||||
bounds: bounds,
|
||||
tilePath: tilePath,
|
||||
token: token
|
||||
}).then(function (data) {
|
||||
_fs["default"].writeFileSync(imgFilename, data);
|
||||
|
||||
console.log('Done!');
|
||||
console.log('\n');
|
||||
})["catch"](function (err) {
|
||||
console.error(err);
|
||||
});
|
||||
};
|
||||
|
||||
if (isMapboxStyle) {
|
||||
if (!token) {
|
||||
raiseError('mapbox access token is required');
|
||||
if (!token) {
|
||||
raiseError('mapbox access token is required');
|
||||
} // load the style then call the render function
|
||||
|
||||
|
||||
var styleURL = (0, _render.normalizeMapboxStyleURL)(styleFilename, token);
|
||||
console.log("requesting mapbox style:".concat(styleFilename, "\nfrom: ").concat(styleURL));
|
||||
(0, _request["default"])(styleURL, function (err, res, body) {
|
||||
if (err) {
|
||||
return raiseError(err);
|
||||
}
|
||||
|
||||
// load the style then call the render function
|
||||
var styleURL = (0, _render.normalizeMapboxStyleURL)(styleFilename, token);
|
||||
console.log('requesting mapbox style:' + styleFilename + '\nfrom: ' + styleURL);
|
||||
(0, _request2.default)(styleURL, function (err, res, body) {
|
||||
if (err) {
|
||||
return raiseError(err);
|
||||
switch (res.statusCode) {
|
||||
case 200:
|
||||
{
|
||||
return renderRequest(JSON.parse(body));
|
||||
}
|
||||
|
||||
switch (res.statusCode) {
|
||||
case 200:
|
||||
{
|
||||
return renderRequest(JSON.parse(body));
|
||||
}
|
||||
case 401:
|
||||
{
|
||||
return raiseError('Mapbox token is not authorized for this style');
|
||||
}
|
||||
default:
|
||||
{
|
||||
return raiseError('Unexpected response for mapbox style request: ' + styleURL + '\n' + res.statusCode);
|
||||
}
|
||||
case 401:
|
||||
{
|
||||
return raiseError('Mapbox token is not authorized for this style');
|
||||
}
|
||||
});
|
||||
|
||||
default:
|
||||
{
|
||||
return raiseError("Unexpected response for mapbox style request: ".concat(styleURL, "\n").concat(res.statusCode));
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// read styleJSON
|
||||
_fs2.default.readFile(styleFilename, function (err, data) {
|
||||
renderRequest(JSON.parse(data));
|
||||
});
|
||||
// read styleJSON
|
||||
_fs["default"].readFile(styleFilename, function (err, data) {
|
||||
renderRequest(JSON.parse(data));
|
||||
});
|
||||
}
|
||||
14
dist/index.js
vendored
@ -1,13 +1,13 @@
|
||||
'use strict';
|
||||
"use strict";
|
||||
|
||||
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports["default"] = void 0;
|
||||
|
||||
var _render = require('./render');
|
||||
var _render = _interopRequireDefault(require("./render"));
|
||||
|
||||
var _render2 = _interopRequireDefault(_render);
|
||||
|
||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||
|
||||
exports.default = _render2.default;
|
||||
var _default = _render["default"];
|
||||
exports["default"] = _default;
|
||||
849
dist/render.js
vendored
@ -1,119 +1,110 @@
|
||||
'use strict';
|
||||
"use strict";
|
||||
|
||||
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
value: true
|
||||
});
|
||||
exports.render = exports.normalizeMapboxGlyphURL = exports.normalizeMapboxSpriteURL = exports.normalizeMapboxStyleURL = exports.isMapboxStyleURL = exports.isMapboxURL = undefined;
|
||||
exports["default"] = exports.render = exports.normalizeMapboxGlyphURL = exports.normalizeMapboxSpriteURL = exports.normalizeMapboxStyleURL = exports.isMapboxStyleURL = exports.isMapboxURL = void 0;
|
||||
|
||||
var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); /* eslint-disable no-new */
|
||||
var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
|
||||
|
||||
var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
|
||||
|
||||
var _fs = _interopRequireDefault(require("fs"));
|
||||
|
||||
var _path = _interopRequireDefault(require("path"));
|
||||
|
||||
var _sharp = _interopRequireDefault(require("sharp"));
|
||||
|
||||
var _zlib = _interopRequireDefault(require("zlib"));
|
||||
|
||||
var _geoViewport = _interopRequireDefault(require("@mapbox/geo-viewport"));
|
||||
|
||||
var _mapboxGlNative = _interopRequireDefault(require("@mapbox/mapbox-gl-native"));
|
||||
|
||||
var _mbtiles = _interopRequireDefault(require("@mapbox/mbtiles"));
|
||||
|
||||
var _request = _interopRequireDefault(require("request"));
|
||||
|
||||
var _url = _interopRequireDefault(require("url"));
|
||||
|
||||
/* eslint-disable no-new */
|
||||
// sharp must be before zlib and other imports or sharp gets wrong version of zlib and breaks on some servers
|
||||
|
||||
|
||||
var _fs = require('fs');
|
||||
|
||||
var _fs2 = _interopRequireDefault(_fs);
|
||||
|
||||
var _path = require('path');
|
||||
|
||||
var _path2 = _interopRequireDefault(_path);
|
||||
|
||||
var _sharp = require('sharp');
|
||||
|
||||
var _sharp2 = _interopRequireDefault(_sharp);
|
||||
|
||||
var _zlib = require('zlib');
|
||||
|
||||
var _zlib2 = _interopRequireDefault(_zlib);
|
||||
|
||||
var _geoViewport = require('@mapbox/geo-viewport');
|
||||
|
||||
var _geoViewport2 = _interopRequireDefault(_geoViewport);
|
||||
|
||||
var _mapboxGlNative = require('@mapbox/mapbox-gl-native');
|
||||
|
||||
var _mapboxGlNative2 = _interopRequireDefault(_mapboxGlNative);
|
||||
|
||||
var _mbtiles = require('@mapbox/mbtiles');
|
||||
|
||||
var _mbtiles2 = _interopRequireDefault(_mbtiles);
|
||||
|
||||
var _request = require('request');
|
||||
|
||||
var _request2 = _interopRequireDefault(_request);
|
||||
|
||||
var _url = require('url');
|
||||
|
||||
var _url2 = _interopRequireDefault(_url);
|
||||
|
||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||
|
||||
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
|
||||
|
||||
var TILE_REGEXP = RegExp('mbtiles://([^/]+)/(\\d+)/(\\d+)/(\\d+)');
|
||||
var MBTILES_REGEXP = /mbtiles:\/\/(\S+?)(?=[/"]+)/gi;
|
||||
|
||||
var isMapboxURL = exports.isMapboxURL = function isMapboxURL(url) {
|
||||
return url.startsWith('mapbox://');
|
||||
};
|
||||
var isMapboxStyleURL = exports.isMapboxStyleURL = function isMapboxStyleURL(url) {
|
||||
return url.startsWith('mapbox://styles/');
|
||||
};
|
||||
var isMBTilesURL = function isMBTilesURL(url) {
|
||||
return url.startsWith('mbtiles://');
|
||||
var isMapboxURL = function isMapboxURL(url) {
|
||||
return url.startsWith('mapbox://');
|
||||
};
|
||||
|
||||
// normalize functions derived from: https://github.com/mapbox/mapbox-gl-js/blob/master/src/util/mapbox.js
|
||||
exports.isMapboxURL = isMapboxURL;
|
||||
|
||||
var isMapboxStyleURL = function isMapboxStyleURL(url) {
|
||||
return url.startsWith('mapbox://styles/');
|
||||
};
|
||||
|
||||
exports.isMapboxStyleURL = isMapboxStyleURL;
|
||||
|
||||
var isMBTilesURL = function isMBTilesURL(url) {
|
||||
return url.startsWith('mbtiles://');
|
||||
}; // normalize functions derived from: https://github.com/mapbox/mapbox-gl-js/blob/master/src/util/mapbox.js
|
||||
|
||||
/**
|
||||
* Normalize a Mapbox source URL to a full URL
|
||||
* @param {string} url - url to mapbox source in style json, e.g. "url": "mapbox://mapbox.mapbox-streets-v7"
|
||||
* @param {string} token - mapbox public token
|
||||
*/
|
||||
var normalizeMapboxSourceURL = function normalizeMapboxSourceURL(url, token) {
|
||||
var urlObject = _url2.default.parse(url);
|
||||
urlObject.query = urlObject.query || {};
|
||||
urlObject.pathname = '/v4/' + url.split('mapbox://')[1] + '.json';
|
||||
urlObject.protocol = 'https';
|
||||
urlObject.host = 'api.mapbox.com';
|
||||
urlObject.query.secure = true;
|
||||
urlObject.query.access_token = token;
|
||||
return _url2.default.format(urlObject);
|
||||
};
|
||||
|
||||
|
||||
var normalizeMapboxSourceURL = function normalizeMapboxSourceURL(url, token) {
|
||||
var urlObject = _url["default"].parse(url);
|
||||
|
||||
urlObject.query = urlObject.query || {};
|
||||
urlObject.pathname = "/v4/".concat(url.split('mapbox://')[1], ".json");
|
||||
urlObject.protocol = 'https';
|
||||
urlObject.host = 'api.mapbox.com';
|
||||
urlObject.query.secure = true;
|
||||
urlObject.query.access_token = token;
|
||||
return _url["default"].format(urlObject);
|
||||
};
|
||||
/**
|
||||
* Normalize a Mapbox tile URL to a full URL
|
||||
* @param {string} url - url to mapbox tile in style json or resolved from source
|
||||
* e.g. mapbox://tiles/mapbox.mapbox-streets-v7/1/0/1.vector.pbf
|
||||
* @param {string} token - mapbox public token
|
||||
*/
|
||||
var normalizeMapboxTileURL = function normalizeMapboxTileURL(url, token) {
|
||||
var urlObject = _url2.default.parse(url);
|
||||
urlObject.query = urlObject.query || {};
|
||||
urlObject.pathname = '/v4' + urlObject.path;
|
||||
urlObject.protocol = 'https';
|
||||
urlObject.host = 'a.tiles.mapbox.com';
|
||||
urlObject.query.access_token = token;
|
||||
return _url2.default.format(urlObject);
|
||||
};
|
||||
|
||||
|
||||
var normalizeMapboxTileURL = function normalizeMapboxTileURL(url, token) {
|
||||
var urlObject = _url["default"].parse(url);
|
||||
|
||||
urlObject.query = urlObject.query || {};
|
||||
urlObject.pathname = "/v4".concat(urlObject.path);
|
||||
urlObject.protocol = 'https';
|
||||
urlObject.host = 'a.tiles.mapbox.com';
|
||||
urlObject.query.access_token = token;
|
||||
return _url["default"].format(urlObject);
|
||||
};
|
||||
/**
|
||||
* Normalize a Mapbox style URL to a full URL
|
||||
* @param {string} url - url to mapbox source in style json, e.g. "url": "mapbox://styles/mapbox/streets-v9"
|
||||
* @param {string} token - mapbox public token
|
||||
*/
|
||||
var normalizeMapboxStyleURL = exports.normalizeMapboxStyleURL = function normalizeMapboxStyleURL(url, token) {
|
||||
var urlObject = _url2.default.parse(url);
|
||||
urlObject.query = {
|
||||
access_token: token,
|
||||
secure: true
|
||||
};
|
||||
urlObject.pathname = 'styles/v1' + urlObject.path;
|
||||
urlObject.protocol = 'https';
|
||||
urlObject.host = 'api.mapbox.com';
|
||||
return _url2.default.format(urlObject);
|
||||
};
|
||||
|
||||
|
||||
var normalizeMapboxStyleURL = function normalizeMapboxStyleURL(url, token) {
|
||||
var urlObject = _url["default"].parse(url);
|
||||
|
||||
urlObject.query = {
|
||||
access_token: token,
|
||||
secure: true
|
||||
};
|
||||
urlObject.pathname = "styles/v1".concat(urlObject.path);
|
||||
urlObject.protocol = 'https';
|
||||
urlObject.host = 'api.mapbox.com';
|
||||
return _url["default"].format(urlObject);
|
||||
};
|
||||
/**
|
||||
* Normalize a Mapbox sprite URL to a full URL
|
||||
* @param {string} url - url to mapbox sprite, e.g. "url": "mapbox://sprites/mapbox/streets-v9.png"
|
||||
@ -121,22 +112,26 @@ var normalizeMapboxStyleURL = exports.normalizeMapboxStyleURL = function normali
|
||||
*
|
||||
* Returns {string} - url, e.g., "https://api.mapbox.com/styles/v1/mapbox/streets-v9/sprite.png?access_token=<token>"
|
||||
*/
|
||||
var normalizeMapboxSpriteURL = exports.normalizeMapboxSpriteURL = function normalizeMapboxSpriteURL(url, token) {
|
||||
var extMatch = /(\.png|\.json)$/g.exec(url);
|
||||
var ratioMatch = /(@\d+x)\./g.exec(url);
|
||||
var trimIndex = Math.min(ratioMatch != null ? ratioMatch.index : Infinity, extMatch.index);
|
||||
var urlObject = _url2.default.parse(url.substring(0, trimIndex));
|
||||
|
||||
var extPart = extMatch[1];
|
||||
var ratioPart = ratioMatch != null ? ratioMatch[1] : '';
|
||||
urlObject.query = urlObject.query || {};
|
||||
urlObject.query.access_token = token;
|
||||
urlObject.pathname = '/styles/v1' + urlObject.path + '/sprite' + ratioPart + extPart;
|
||||
urlObject.protocol = 'https';
|
||||
urlObject.host = 'api.mapbox.com';
|
||||
return _url2.default.format(urlObject);
|
||||
|
||||
exports.normalizeMapboxStyleURL = normalizeMapboxStyleURL;
|
||||
|
||||
var normalizeMapboxSpriteURL = function normalizeMapboxSpriteURL(url, token) {
|
||||
var extMatch = /(\.png|\.json)$/g.exec(url);
|
||||
var ratioMatch = /(@\d+x)\./g.exec(url);
|
||||
var trimIndex = Math.min(ratioMatch != null ? ratioMatch.index : Infinity, extMatch.index);
|
||||
|
||||
var urlObject = _url["default"].parse(url.substring(0, trimIndex));
|
||||
|
||||
var extPart = extMatch[1];
|
||||
var ratioPart = ratioMatch != null ? ratioMatch[1] : '';
|
||||
urlObject.query = urlObject.query || {};
|
||||
urlObject.query.access_token = token;
|
||||
urlObject.pathname = "/styles/v1".concat(urlObject.path, "/sprite").concat(ratioPart).concat(extPart);
|
||||
urlObject.protocol = 'https';
|
||||
urlObject.host = 'api.mapbox.com';
|
||||
return _url["default"].format(urlObject);
|
||||
};
|
||||
|
||||
/**
|
||||
* Normalize a Mapbox glyph URL to a full URL
|
||||
* @param {string} url - url to mapbox sprite, e.g. "url": "mapbox://sprites/mapbox/streets-v9.png"
|
||||
@ -144,25 +139,32 @@ var normalizeMapboxSpriteURL = exports.normalizeMapboxSpriteURL = function norma
|
||||
*
|
||||
* Returns {string} - url, e.g., "https://api.mapbox.com/styles/v1/mapbox/streets-v9/sprite.png?access_token=<token>"
|
||||
*/
|
||||
var normalizeMapboxGlyphURL = exports.normalizeMapboxGlyphURL = function normalizeMapboxGlyphURL(url, token) {
|
||||
var urlObject = _url2.default.parse(url);
|
||||
urlObject.query = urlObject.query || {};
|
||||
urlObject.query.access_token = token;
|
||||
urlObject.pathname = '/fonts/v1' + urlObject.path;
|
||||
urlObject.protocol = 'https';
|
||||
urlObject.host = 'api.mapbox.com';
|
||||
return _url2.default.format(urlObject);
|
||||
};
|
||||
|
||||
|
||||
exports.normalizeMapboxSpriteURL = normalizeMapboxSpriteURL;
|
||||
|
||||
var normalizeMapboxGlyphURL = function normalizeMapboxGlyphURL(url, token) {
|
||||
var urlObject = _url["default"].parse(url);
|
||||
|
||||
urlObject.query = urlObject.query || {};
|
||||
urlObject.query.access_token = token;
|
||||
urlObject.pathname = "/fonts/v1".concat(urlObject.path);
|
||||
urlObject.protocol = 'https';
|
||||
urlObject.host = 'api.mapbox.com';
|
||||
return _url["default"].format(urlObject);
|
||||
};
|
||||
/**
|
||||
* Very simplistic function that splits out mbtiles service name from the URL
|
||||
*
|
||||
* @param {String} url - URL to resolve
|
||||
*/
|
||||
var resolveNamefromURL = function resolveNamefromURL(url) {
|
||||
return url.split('://')[1].split('/')[0];
|
||||
};
|
||||
|
||||
|
||||
exports.normalizeMapboxGlyphURL = normalizeMapboxGlyphURL;
|
||||
|
||||
var resolveNamefromURL = function resolveNamefromURL(url) {
|
||||
return url.split('://')[1].split('/')[0];
|
||||
};
|
||||
/**
|
||||
* Resolve a URL of a local mbtiles file to a file path
|
||||
* Expected to follow this format "mbtiles://<service_name>/*"
|
||||
@ -170,14 +172,15 @@ var resolveNamefromURL = function resolveNamefromURL(url) {
|
||||
* @param {String} tilePath - path containing mbtiles files
|
||||
* @param {String} url - url of a data source in style.json file.
|
||||
*/
|
||||
var resolveMBTilesURL = function resolveMBTilesURL(tilePath, url) {
|
||||
return _path2.default.format({
|
||||
dir: tilePath,
|
||||
name: resolveNamefromURL(url),
|
||||
ext: '.mbtiles'
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
var resolveMBTilesURL = function resolveMBTilesURL(tilePath, url) {
|
||||
return _path["default"].format({
|
||||
dir: tilePath,
|
||||
name: resolveNamefromURL(url),
|
||||
ext: '.mbtiles'
|
||||
});
|
||||
};
|
||||
/**
|
||||
* Given a URL to a local mbtiles file, get the TileJSON for that to load correct tiles.
|
||||
*
|
||||
@ -185,48 +188,45 @@ var resolveMBTilesURL = function resolveMBTilesURL(tilePath, url) {
|
||||
* @param {String} url - url of a data source in style.json file.
|
||||
* @param {function} callback - function to call with (err, {data}).
|
||||
*/
|
||||
|
||||
|
||||
var getLocalTileJSON = function getLocalTileJSON(tilePath, url, callback) {
|
||||
var mbtilesFilename = resolveMBTilesURL(tilePath, url);
|
||||
var service = resolveNamefromURL(url);
|
||||
|
||||
new _mbtiles2.default(mbtilesFilename, function (err, mbtiles) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
return null;
|
||||
}
|
||||
|
||||
mbtiles.getInfo(function (infoErr, info) {
|
||||
if (infoErr) {
|
||||
callback(infoErr);
|
||||
return null;
|
||||
}
|
||||
|
||||
var minzoom = info.minzoom,
|
||||
maxzoom = info.maxzoom,
|
||||
center = info.center,
|
||||
bounds = info.bounds,
|
||||
format = info.format;
|
||||
|
||||
|
||||
var ext = format === 'pbf' ? '.pbf' : '';
|
||||
|
||||
var tileJSON = {
|
||||
tilejson: '1.0.0',
|
||||
tiles: ['mbtiles://' + service + '/{z}/{x}/{y}' + ext],
|
||||
minzoom: minzoom,
|
||||
maxzoom: maxzoom,
|
||||
center: center,
|
||||
bounds: bounds
|
||||
};
|
||||
|
||||
callback(null, { data: Buffer.from(JSON.stringify(tileJSON)) });
|
||||
return null;
|
||||
});
|
||||
var mbtilesFilename = resolveMBTilesURL(tilePath, url);
|
||||
var service = resolveNamefromURL(url);
|
||||
new _mbtiles["default"](mbtilesFilename, function (err, mbtiles) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
return null;
|
||||
}
|
||||
|
||||
mbtiles.getInfo(function (infoErr, info) {
|
||||
if (infoErr) {
|
||||
callback(infoErr);
|
||||
return null;
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
var minzoom = info.minzoom,
|
||||
maxzoom = info.maxzoom,
|
||||
center = info.center,
|
||||
bounds = info.bounds,
|
||||
format = info.format;
|
||||
var ext = format === 'pbf' ? '.pbf' : '';
|
||||
var tileJSON = {
|
||||
tilejson: '1.0.0',
|
||||
tiles: ["mbtiles://".concat(service, "/{z}/{x}/{y}").concat(ext)],
|
||||
minzoom: minzoom,
|
||||
maxzoom: maxzoom,
|
||||
center: center,
|
||||
bounds: bounds
|
||||
};
|
||||
callback(null, {
|
||||
data: Buffer.from(JSON.stringify(tileJSON))
|
||||
});
|
||||
return null;
|
||||
});
|
||||
return null;
|
||||
});
|
||||
};
|
||||
/**
|
||||
* Fetch a tile from a local mbtiles file.
|
||||
*
|
||||
@ -234,83 +234,91 @@ var getLocalTileJSON = function getLocalTileJSON(tilePath, url, callback) {
|
||||
* @param {String} url - url of a data source in style.json file.
|
||||
* @param {function} callback - function to call with (err, {data}).
|
||||
*/
|
||||
|
||||
|
||||
var getLocalTile = function getLocalTile(tilePath, url, callback) {
|
||||
var matches = url.match(TILE_REGEXP);
|
||||
var matches = url.match(TILE_REGEXP);
|
||||
|
||||
var _matches$slice = matches.slice(matches.length - 3, matches.length),
|
||||
_matches$slice2 = _slicedToArray(_matches$slice, 3),
|
||||
z = _matches$slice2[0],
|
||||
x = _matches$slice2[1],
|
||||
y = _matches$slice2[2];
|
||||
var _matches$slice = matches.slice(matches.length - 3, matches.length),
|
||||
_matches$slice2 = (0, _slicedToArray2["default"])(_matches$slice, 3),
|
||||
z = _matches$slice2[0],
|
||||
x = _matches$slice2[1],
|
||||
y = _matches$slice2[2];
|
||||
|
||||
var isVector = _path2.default.extname(url) === '.pbf';
|
||||
var mbtilesFile = resolveMBTilesURL(tilePath, url);
|
||||
|
||||
new _mbtiles2.default(mbtilesFile, function (err, mbtiles) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
return null;
|
||||
}
|
||||
|
||||
mbtiles.getTile(z, x, y, function (tileErr, data) {
|
||||
if (tileErr) {
|
||||
// console.log(`error fetching tile: z:${z} x:${x} y:${y} from ${mbtilesFile}\n${tileErr}`)
|
||||
callback(null, {});
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isVector) {
|
||||
// if the tile is compressed, unzip it (for vector tiles only!)
|
||||
_zlib2.default.unzip(data, function (unzipErr, unzippedData) {
|
||||
callback(unzipErr, { data: unzippedData });
|
||||
});
|
||||
} else {
|
||||
callback(null, { data: data });
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
var isVector = _path["default"].extname(url) === '.pbf';
|
||||
var mbtilesFile = resolveMBTilesURL(tilePath, url);
|
||||
new _mbtiles["default"](mbtilesFile, function (err, mbtiles) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
return null;
|
||||
}
|
||||
|
||||
mbtiles.getTile(z, x, y, function (tileErr, data) {
|
||||
if (tileErr) {
|
||||
// console.log(`error fetching tile: z:${z} x:${x} y:${y} from ${mbtilesFile}\n${tileErr}`)
|
||||
callback(null, {});
|
||||
return null;
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
if (isVector) {
|
||||
// if the tile is compressed, unzip it (for vector tiles only!)
|
||||
_zlib["default"].unzip(data, function (unzipErr, unzippedData) {
|
||||
callback(unzipErr, {
|
||||
data: unzippedData
|
||||
});
|
||||
});
|
||||
} else {
|
||||
callback(null, {
|
||||
data: data
|
||||
});
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
return null;
|
||||
});
|
||||
};
|
||||
/**
|
||||
* Fetch a remotely hosted asset: tile, sprite, etc
|
||||
*
|
||||
* @param {String} url - URL of the asset
|
||||
* @param {function} callback - callback to call with (err, {data})
|
||||
*/
|
||||
|
||||
|
||||
var getRemoteAsset = function getRemoteAsset(url, callback) {
|
||||
(0, _request2.default)({
|
||||
url: url,
|
||||
encoding: null,
|
||||
gzip: true
|
||||
}, function (err, res, data) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
(0, _request["default"])({
|
||||
url: url,
|
||||
encoding: null,
|
||||
gzip: true
|
||||
}, function (err, res, data) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
switch (res.statusCode) {
|
||||
case 200:
|
||||
{
|
||||
return callback(null, {
|
||||
data: data
|
||||
});
|
||||
}
|
||||
|
||||
switch (res.statusCode) {
|
||||
case 200:
|
||||
{
|
||||
return callback(null, { data: data });
|
||||
}
|
||||
case 204:
|
||||
{
|
||||
// No data for this url
|
||||
return callback(null, {});
|
||||
}
|
||||
default:
|
||||
{
|
||||
// assume error
|
||||
console.log('Error with request for: ' + url + '\nstatus: ' + res.statusCode);
|
||||
return callback(new Error('Error with request for: ' + url + '\nstatus: ' + res.statusCode));
|
||||
}
|
||||
case 204:
|
||||
{
|
||||
// No data for this url
|
||||
return callback(null, {});
|
||||
}
|
||||
});
|
||||
|
||||
default:
|
||||
{
|
||||
// assume error
|
||||
console.log("Error with request for: ".concat(url, "\nstatus: ").concat(res.statusCode));
|
||||
return callback(new Error("Error with request for: ".concat(url, "\nstatus: ").concat(res.statusCode)));
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Render a map using Mapbox GL, based on layers specified in style.
|
||||
* Returns a Promise with the PNG image data as its first parameter for the map image.
|
||||
@ -325,207 +333,230 @@ var getRemoteAsset = function getRemoteAsset(url, callback) {
|
||||
* @param {String} tilePath - path to directory containing local mbtiles files that are
|
||||
* referenced from the style.json as "mbtiles://<tileset>"
|
||||
*/
|
||||
var render = exports.render = function render(style) {
|
||||
var width = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1024;
|
||||
var height = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 1024;
|
||||
var options = arguments[3];
|
||||
return new Promise(function (resolve, reject) {
|
||||
var _options$bounds = options.bounds,
|
||||
bounds = _options$bounds === undefined ? null : _options$bounds,
|
||||
_options$token = options.token,
|
||||
token = _options$token === undefined ? null : _options$token,
|
||||
_options$ratio = options.ratio,
|
||||
ratio = _options$ratio === undefined ? 1 : _options$ratio;
|
||||
var _options$center = options.center,
|
||||
center = _options$center === undefined ? null : _options$center,
|
||||
_options$zoom = options.zoom,
|
||||
zoom = _options$zoom === undefined ? null : _options$zoom,
|
||||
_options$tilePath = options.tilePath,
|
||||
tilePath = _options$tilePath === undefined ? null : _options$tilePath;
|
||||
|
||||
|
||||
if (!style) {
|
||||
throw new Error('style is a required parameter');
|
||||
var render = function render(style) {
|
||||
var width = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1024;
|
||||
var height = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 1024;
|
||||
var options = arguments.length > 3 ? arguments[3] : undefined;
|
||||
return new Promise(function (resolve, reject) {
|
||||
var _options$bounds = options.bounds,
|
||||
bounds = _options$bounds === void 0 ? null : _options$bounds,
|
||||
_options$token = options.token,
|
||||
token = _options$token === void 0 ? null : _options$token,
|
||||
_options$ratio = options.ratio,
|
||||
ratio = _options$ratio === void 0 ? 1 : _options$ratio;
|
||||
var _options$center = options.center,
|
||||
center = _options$center === void 0 ? null : _options$center,
|
||||
_options$zoom = options.zoom,
|
||||
zoom = _options$zoom === void 0 ? null : _options$zoom,
|
||||
_options$tilePath = options.tilePath,
|
||||
tilePath = _options$tilePath === void 0 ? null : _options$tilePath;
|
||||
|
||||
if (!style) {
|
||||
throw new Error('style is a required parameter');
|
||||
}
|
||||
|
||||
if (!(width && height)) {
|
||||
throw new Error('width and height are required parameters and must be non-zero');
|
||||
}
|
||||
|
||||
if (center !== null) {
|
||||
if (center.length !== 2) {
|
||||
throw new Error("Center must be longitude,latitude. Invalid value found: ".concat((0, _toConsumableArray2["default"])(center)));
|
||||
}
|
||||
|
||||
if (Math.abs(center[0]) > 180) {
|
||||
throw new Error("Center longitude is outside world bounds (-180 to 180 deg): ".concat(center[0]));
|
||||
}
|
||||
|
||||
if (Math.abs(center[1]) > 90) {
|
||||
throw new Error("Center latitude is outside world bounds (-90 to 90 deg): ".concat(center[1]));
|
||||
}
|
||||
}
|
||||
|
||||
if (zoom !== null && (zoom < 0 || zoom > 22)) {
|
||||
throw new Error("Zoom level is outside supported range (0-22): ".concat(zoom));
|
||||
}
|
||||
|
||||
if (bounds !== null) {
|
||||
if (bounds.length !== 4) {
|
||||
throw new Error("Bounds must be west,south,east,north. Invalid value found: ".concat((0, _toConsumableArray2["default"])(bounds)));
|
||||
}
|
||||
} // calculate zoom and center from bounds and image dimensions
|
||||
|
||||
|
||||
if (bounds !== null && (zoom === null || center === null)) {
|
||||
var viewport = _geoViewport["default"].viewport(bounds, [width, height], undefined, undefined, undefined, true);
|
||||
|
||||
zoom = Math.max(viewport.zoom - 1, 0);
|
||||
/* eslint-disable prefer-destructuring */
|
||||
|
||||
center = viewport.center;
|
||||
} // validate that all local mbtiles referenced in style are
|
||||
// present in tilePath and that tilePath is not null
|
||||
|
||||
|
||||
if (tilePath) {
|
||||
tilePath = _path["default"].normalize(tilePath);
|
||||
}
|
||||
|
||||
var localMbtilesMatches = JSON.stringify(style).match(MBTILES_REGEXP);
|
||||
|
||||
if (localMbtilesMatches && !tilePath) {
|
||||
throw new Error('Style has local mbtiles file sources, but no tilePath is set');
|
||||
}
|
||||
|
||||
if (localMbtilesMatches) {
|
||||
localMbtilesMatches.forEach(function (name) {
|
||||
var mbtileFilename = _path["default"].normalize(_path["default"].format({
|
||||
dir: tilePath,
|
||||
name: name.split('://')[1],
|
||||
ext: '.mbtiles'
|
||||
}));
|
||||
|
||||
if (!_fs["default"].existsSync(mbtileFilename)) {
|
||||
throw new Error("Mbtiles file ".concat(_path["default"].format({
|
||||
name: name,
|
||||
ext: '.mbtiles'
|
||||
}), " in style file is not found in: ").concat(tilePath));
|
||||
}
|
||||
if (!(width && height)) {
|
||||
throw new Error('width and height are required parameters and must be non-zero');
|
||||
});
|
||||
} // Options object for configuring loading of map data sources.
|
||||
// Note: could not find a way to make this work with mapbox vector sources and styles!
|
||||
|
||||
|
||||
var mapOptions = {
|
||||
request: function request(req, callback) {
|
||||
var url = req.url,
|
||||
kind = req.kind;
|
||||
var isMapbox = isMapboxURL(url);
|
||||
|
||||
if (isMapbox && !token) {
|
||||
throw new Error('ERROR: mapbox access token is required');
|
||||
}
|
||||
|
||||
if (center !== null) {
|
||||
if (center.length !== 2) {
|
||||
throw new Error('Center must be longitude,latitude. Invalid value found: ' + [].concat(_toConsumableArray(center)));
|
||||
}
|
||||
|
||||
if (Math.abs(center[0]) > 180) {
|
||||
throw new Error('Center longitude is outside world bounds (-180 to 180 deg): ' + center[0]);
|
||||
}
|
||||
|
||||
if (Math.abs(center[1]) > 90) {
|
||||
throw new Error('Center latitude is outside world bounds (-90 to 90 deg): ' + center[1]);
|
||||
}
|
||||
}
|
||||
|
||||
if (zoom !== null && (zoom < 0 || zoom > 22)) {
|
||||
throw new Error('Zoom level is outside supported range (0-22): ' + zoom);
|
||||
}
|
||||
|
||||
if (bounds !== null) {
|
||||
if (bounds.length !== 4) {
|
||||
throw new Error('Bounds must be west,south,east,north. Invalid value found: ' + [].concat(_toConsumableArray(bounds)));
|
||||
}
|
||||
}
|
||||
|
||||
// calculate zoom and center from bounds and image dimensions
|
||||
if (bounds !== null && (zoom === null || center === null)) {
|
||||
var viewport = _geoViewport2.default.viewport(bounds, [width, height], undefined, undefined, undefined, true);
|
||||
zoom = Math.max(viewport.zoom - 1, 0);
|
||||
/* eslint-disable prefer-destructuring */
|
||||
center = viewport.center;
|
||||
}
|
||||
|
||||
// validate that all local mbtiles referenced in style are
|
||||
// present in tilePath and that tilePath is not null
|
||||
if (tilePath) {
|
||||
tilePath = _path2.default.normalize(tilePath);
|
||||
}
|
||||
|
||||
var localMbtilesMatches = JSON.stringify(style).match(MBTILES_REGEXP);
|
||||
if (localMbtilesMatches && !tilePath) {
|
||||
throw new Error('Style has local mbtiles file sources, but no tilePath is set');
|
||||
}
|
||||
|
||||
if (localMbtilesMatches) {
|
||||
localMbtilesMatches.forEach(function (name) {
|
||||
var mbtileFilename = _path2.default.normalize(_path2.default.format({ dir: tilePath, name: name.split('://')[1], ext: '.mbtiles' }));
|
||||
if (!_fs2.default.existsSync(mbtileFilename)) {
|
||||
throw new Error('Mbtiles file ' + _path2.default.format({
|
||||
name: name,
|
||||
ext: '.mbtiles'
|
||||
}) + ' in style file is not found in: ' + tilePath);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Options object for configuring loading of map data sources.
|
||||
// Note: could not find a way to make this work with mapbox vector sources and styles!
|
||||
var mapOptions = {
|
||||
request: function request(req, callback) {
|
||||
var url = req.url,
|
||||
kind = req.kind;
|
||||
|
||||
|
||||
var isMapbox = isMapboxURL(url);
|
||||
if (isMapbox && !token) {
|
||||
throw new Error('ERROR: mapbox access token is required');
|
||||
}
|
||||
|
||||
try {
|
||||
switch (kind) {
|
||||
case 2:
|
||||
{
|
||||
// source
|
||||
if (isMBTilesURL(url)) {
|
||||
getLocalTileJSON(tilePath, url, callback);
|
||||
} else if (isMapbox) {
|
||||
getRemoteAsset(normalizeMapboxSourceURL(url, token), callback);
|
||||
} else {
|
||||
getRemoteAsset(url, callback);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 3:
|
||||
{
|
||||
// tile
|
||||
if (isMBTilesURL(url)) {
|
||||
getLocalTile(tilePath, url, callback);
|
||||
} else if (isMapbox) {
|
||||
// This seems to be due to a bug in how the mapbox tile
|
||||
// JSON is handled within mapbox-gl-native
|
||||
// since it returns fully resolved tiles!
|
||||
getRemoteAsset(normalizeMapboxTileURL(url, token), callback);
|
||||
} else {
|
||||
getRemoteAsset(url, callback);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 4:
|
||||
{
|
||||
// glyph
|
||||
getRemoteAsset(isMapbox ? normalizeMapboxGlyphURL(url, token) : _url2.default.parse(url), callback);
|
||||
break;
|
||||
}
|
||||
case 5:
|
||||
{
|
||||
// sprite image
|
||||
getRemoteAsset(isMapbox ? normalizeMapboxSpriteURL(url, token) : _url2.default.parse(url), callback);
|
||||
break;
|
||||
}
|
||||
case 6:
|
||||
{
|
||||
// sprite json
|
||||
getRemoteAsset(isMapbox ? normalizeMapboxSpriteURL(url, token) : _url2.default.parse(url), callback);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
// NOT HANDLED!
|
||||
throw new Error('ERROR: Request kind not handled: ' + kind);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error while making tile request: %j', err);
|
||||
callback(err);
|
||||
}
|
||||
},
|
||||
ratio: ratio
|
||||
};
|
||||
|
||||
var map = new _mapboxGlNative2.default.Map(mapOptions);
|
||||
map.load(style);
|
||||
|
||||
map.render({
|
||||
zoom: zoom,
|
||||
center: center,
|
||||
height: height,
|
||||
width: width
|
||||
}, function (err, buffer) {
|
||||
if (err) {
|
||||
console.error('Error rendering map');
|
||||
console.error(err);
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
map.release(); // release map resources to prevent reusing in future render requests
|
||||
|
||||
// Un-premultiply pixel values
|
||||
// Mapbox GL buffer contains premultiplied values, which are not handled correctly by sharp
|
||||
// https://github.com/mapbox/mapbox-gl-native/issues/9124
|
||||
// since we are dealing with 8-bit RGBA values, normalize alpha onto 0-255 scale and divide
|
||||
// it out of RGB values
|
||||
for (var i = 0; i < buffer.length; i += 4) {
|
||||
var alpha = buffer[i + 3];
|
||||
var norm = alpha / 255;
|
||||
if (alpha === 0) {
|
||||
buffer[i] = 0;
|
||||
buffer[i + 1] = 0;
|
||||
buffer[i + 2] = 0;
|
||||
try {
|
||||
switch (kind) {
|
||||
case 2:
|
||||
{
|
||||
// source
|
||||
if (isMBTilesURL(url)) {
|
||||
getLocalTileJSON(tilePath, url, callback);
|
||||
} else if (isMapbox) {
|
||||
getRemoteAsset(normalizeMapboxSourceURL(url, token), callback);
|
||||
} else {
|
||||
buffer[i] = buffer[i] / norm;
|
||||
buffer[i + 1] = buffer[i + 1] / norm;
|
||||
buffer[i + 2] = buffer[i + 2] / norm;
|
||||
getRemoteAsset(url, callback);
|
||||
}
|
||||
}
|
||||
|
||||
// Convert raw image buffer to PNG
|
||||
try {
|
||||
return (0, _sharp2.default)(buffer, { raw: { width: width * ratio, height: height * ratio, channels: 4 } }).png().toBuffer().then(resolve).catch(reject);
|
||||
} catch (pngErr) {
|
||||
console.error('Error encoding PNG');
|
||||
console.error(pngErr);
|
||||
return reject(pngErr);
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case 3:
|
||||
{
|
||||
// tile
|
||||
if (isMBTilesURL(url)) {
|
||||
getLocalTile(tilePath, url, callback);
|
||||
} else if (isMapbox) {
|
||||
// This seems to be due to a bug in how the mapbox tile
|
||||
// JSON is handled within mapbox-gl-native
|
||||
// since it returns fully resolved tiles!
|
||||
getRemoteAsset(normalizeMapboxTileURL(url, token), callback);
|
||||
} else {
|
||||
getRemoteAsset(url, callback);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 4:
|
||||
{
|
||||
// glyph
|
||||
getRemoteAsset(isMapbox ? normalizeMapboxGlyphURL(url, token) : _url["default"].parse(url), callback);
|
||||
break;
|
||||
}
|
||||
|
||||
case 5:
|
||||
{
|
||||
// sprite image
|
||||
getRemoteAsset(isMapbox ? normalizeMapboxSpriteURL(url, token) : _url["default"].parse(url), callback);
|
||||
break;
|
||||
}
|
||||
|
||||
case 6:
|
||||
{
|
||||
// sprite json
|
||||
getRemoteAsset(isMapbox ? normalizeMapboxSpriteURL(url, token) : _url["default"].parse(url), callback);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
// NOT HANDLED!
|
||||
throw new Error("ERROR: Request kind not handled: ".concat(kind));
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error while making tile request: %j', err);
|
||||
callback(err);
|
||||
}
|
||||
},
|
||||
ratio: ratio
|
||||
};
|
||||
var map = new _mapboxGlNative["default"].Map(mapOptions);
|
||||
map.load(style);
|
||||
map.render({
|
||||
zoom: zoom,
|
||||
center: center,
|
||||
height: height,
|
||||
width: width
|
||||
}, function (err, buffer) {
|
||||
if (err) {
|
||||
console.error('Error rendering map');
|
||||
console.error(err);
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
map.release(); // release map resources to prevent reusing in future render requests
|
||||
// Un-premultiply pixel values
|
||||
// Mapbox GL buffer contains premultiplied values, which are not handled correctly by sharp
|
||||
// https://github.com/mapbox/mapbox-gl-native/issues/9124
|
||||
// since we are dealing with 8-bit RGBA values, normalize alpha onto 0-255 scale and divide
|
||||
// it out of RGB values
|
||||
|
||||
for (var i = 0; i < buffer.length; i += 4) {
|
||||
var alpha = buffer[i + 3];
|
||||
var norm = alpha / 255;
|
||||
|
||||
if (alpha === 0) {
|
||||
buffer[i] = 0;
|
||||
buffer[i + 1] = 0;
|
||||
buffer[i + 2] = 0;
|
||||
} else {
|
||||
buffer[i] = buffer[i] / norm;
|
||||
buffer[i + 1] = buffer[i + 1] / norm;
|
||||
buffer[i + 2] = buffer[i + 2] / norm;
|
||||
}
|
||||
} // Convert raw image buffer to PNG
|
||||
|
||||
|
||||
try {
|
||||
return (0, _sharp["default"])(buffer, {
|
||||
raw: {
|
||||
width: width * ratio,
|
||||
height: height * ratio,
|
||||
channels: 4
|
||||
}
|
||||
}).png().toBuffer().then(resolve)["catch"](reject);
|
||||
} catch (pngErr) {
|
||||
console.error('Error encoding PNG');
|
||||
console.error(pngErr);
|
||||
return reject(pngErr);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
exports.default = render;
|
||||
exports.render = render;
|
||||
var _default = render;
|
||||
exports["default"] = _default;
|
||||
347
dist/server.js
vendored
@ -1,203 +1,228 @@
|
||||
#!/usr/bin/env node
|
||||
'use strict';
|
||||
"use strict";
|
||||
|
||||
var _restify = require('restify');
|
||||
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
||||
|
||||
var _restify2 = _interopRequireDefault(_restify);
|
||||
var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
|
||||
|
||||
var _nodeRestifyValidation = require('node-restify-validation');
|
||||
var _restify = _interopRequireDefault(require("restify"));
|
||||
|
||||
var _nodeRestifyValidation2 = _interopRequireDefault(_nodeRestifyValidation);
|
||||
var _nodeRestifyValidation = _interopRequireDefault(require("node-restify-validation"));
|
||||
|
||||
var _restifyErrors = require('restify-errors');
|
||||
var _restifyErrors = _interopRequireDefault(require("restify-errors"));
|
||||
|
||||
var _restifyErrors2 = _interopRequireDefault(_restifyErrors);
|
||||
var _commander = _interopRequireDefault(require("commander"));
|
||||
|
||||
var _commander = require('commander');
|
||||
var _package = require("../package.json");
|
||||
|
||||
var _commander2 = _interopRequireDefault(_commander);
|
||||
|
||||
var _package = require('../package.json');
|
||||
|
||||
var _render = require('./render');
|
||||
|
||||
var _render2 = _interopRequireDefault(_render);
|
||||
|
||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||
|
||||
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
|
||||
var _render = _interopRequireDefault(require("./render"));
|
||||
|
||||
var parseListToFloat = function parseListToFloat(text) {
|
||||
return text.split(',').map(Number);
|
||||
return text.split(',').map(Number);
|
||||
};
|
||||
|
||||
var PARAMS = {
|
||||
style: { isRequired: true, isString: true },
|
||||
width: { isRequired: true, isInt: true },
|
||||
height: { isRequired: true, isInt: true },
|
||||
zoom: { isRequired: false, isDecimal: true },
|
||||
ratio: { isRequired: false, isDecimal: true },
|
||||
token: { isRequired: false, isString: true }
|
||||
style: {
|
||||
isRequired: true,
|
||||
isString: true
|
||||
},
|
||||
width: {
|
||||
isRequired: true,
|
||||
isInt: true
|
||||
},
|
||||
height: {
|
||||
isRequired: true,
|
||||
isInt: true
|
||||
},
|
||||
zoom: {
|
||||
isRequired: false,
|
||||
isDecimal: true
|
||||
},
|
||||
ratio: {
|
||||
isRequired: false,
|
||||
isDecimal: true
|
||||
},
|
||||
token: {
|
||||
isRequired: false,
|
||||
isString: true
|
||||
}
|
||||
};
|
||||
|
||||
var renderImage = function renderImage(params, response, next, tilePath) {
|
||||
var width = params.width,
|
||||
height = params.height,
|
||||
_params$token = params.token,
|
||||
token = _params$token === undefined ? null : _params$token;
|
||||
var style = params.style,
|
||||
_params$zoom = params.zoom,
|
||||
zoom = _params$zoom === undefined ? null : _params$zoom,
|
||||
_params$center = params.center,
|
||||
center = _params$center === undefined ? null : _params$center,
|
||||
_params$bounds = params.bounds,
|
||||
bounds = _params$bounds === undefined ? null : _params$bounds,
|
||||
_params$ratio = params.ratio,
|
||||
ratio = _params$ratio === undefined ? 1 : _params$ratio;
|
||||
|
||||
|
||||
if (typeof style === 'string') {
|
||||
try {
|
||||
style = JSON.parse(style);
|
||||
} catch (jsonErr) {
|
||||
console.error('Error parsing JSON style in request: %j', jsonErr);
|
||||
return next(new _restifyErrors2.default.BadRequestError({ cause: jsonErr }, 'Error parsing JSON style'));
|
||||
}
|
||||
}
|
||||
|
||||
if (center !== null) {
|
||||
if (typeof center === 'string') {
|
||||
center = parseListToFloat(center);
|
||||
}
|
||||
|
||||
if (center.length !== 2) {
|
||||
return next(new _restifyErrors2.default.BadRequestError('Center must be longitude,latitude. Invalid value found: ' + [].concat(_toConsumableArray(center))));
|
||||
}
|
||||
|
||||
if (Number.isNaN(center[0]) || Math.abs(center[0]) > 180) {
|
||||
return next(new _restifyErrors2.default.BadRequestError('Center longitude is outside world bounds (-180 to 180 deg): ' + center[0]));
|
||||
}
|
||||
|
||||
if (Number.isNaN(center[1]) || Math.abs(center[1]) > 90) {
|
||||
return next(new _restifyErrors2.default.BadRequestError('Center latitude is outside world bounds (-90 to 90 deg): ' + center[1]));
|
||||
}
|
||||
}
|
||||
if (zoom !== null) {
|
||||
zoom = parseFloat(zoom);
|
||||
if (zoom < 0 || zoom > 22) {
|
||||
return next(new _restifyErrors2.default.BadRequestError('Zoom level is outside supported range (0-22): ' + zoom));
|
||||
}
|
||||
}
|
||||
if (ratio !== null) {
|
||||
ratio = parseInt(ratio);
|
||||
if (!ratio || ratio < 1) {
|
||||
return next(new _restifyErrors2.default.BadRequestError('Ratio is outside supported range (>=1): ' + ratio));
|
||||
}
|
||||
}
|
||||
if (bounds !== null) {
|
||||
if (typeof bounds === 'string') {
|
||||
bounds = parseListToFloat(bounds);
|
||||
}
|
||||
|
||||
if (bounds.length !== 4) {
|
||||
return next(new _restifyErrors2.default.BadRequestError('Bounds must be west,south,east,north. Invalid value found: ' + [].concat(_toConsumableArray(bounds))));
|
||||
}
|
||||
bounds.forEach(function (b) {
|
||||
if (Number.isNaN(b)) {
|
||||
return next(new _restifyErrors2.default.BadRequestError('Bounds must be west,south,east,north. Invalid value found: ' + [].concat(_toConsumableArray(bounds))));
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
if (!(center && zoom !== null || bounds)) {
|
||||
return next(new _restifyErrors2.default.BadRequestError('Either center and zoom OR bounds must be provided'));
|
||||
}
|
||||
var width = params.width,
|
||||
height = params.height,
|
||||
_params$token = params.token,
|
||||
token = _params$token === void 0 ? null : _params$token;
|
||||
var style = params.style,
|
||||
_params$zoom = params.zoom,
|
||||
zoom = _params$zoom === void 0 ? null : _params$zoom,
|
||||
_params$center = params.center,
|
||||
center = _params$center === void 0 ? null : _params$center,
|
||||
_params$bounds = params.bounds,
|
||||
bounds = _params$bounds === void 0 ? null : _params$bounds,
|
||||
_params$ratio = params.ratio,
|
||||
ratio = _params$ratio === void 0 ? 1 : _params$ratio;
|
||||
|
||||
if (typeof style === 'string') {
|
||||
try {
|
||||
(0, _render2.default)(style, parseInt(width, 10), parseInt(height, 10), {
|
||||
zoom: zoom,
|
||||
center: center,
|
||||
bounds: bounds,
|
||||
tilePath: tilePath,
|
||||
ratio: ratio,
|
||||
token: token
|
||||
}).then(function (data, rejected) {
|
||||
if (rejected) {
|
||||
console.error('render request rejected', rejected);
|
||||
return next(new _restifyErrors2.default.InternalServerError({ cause: rejected }, 'Error processing render request'));
|
||||
}
|
||||
return response.sendRaw(200, data, { 'content-type': 'image/png' });
|
||||
}).catch(function (err) {
|
||||
console.error('Error processing render request', err);
|
||||
return next(new _restifyErrors2.default.InternalServerError({ cause: err }, 'Error processing render request'));
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('Error processing render request', err);
|
||||
return next(new _restifyErrors2.default.InternalServerError({ cause: err }, 'Error processing render request'));
|
||||
style = JSON.parse(style);
|
||||
} catch (jsonErr) {
|
||||
console.error('Error parsing JSON style in request: %j', jsonErr);
|
||||
return next(new _restifyErrors["default"].BadRequestError({
|
||||
cause: jsonErr
|
||||
}, 'Error parsing JSON style'));
|
||||
}
|
||||
}
|
||||
|
||||
if (center !== null) {
|
||||
if (typeof center === 'string') {
|
||||
center = parseListToFloat(center);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
if (center.length !== 2) {
|
||||
return next(new _restifyErrors["default"].BadRequestError("Center must be longitude,latitude. Invalid value found: ".concat((0, _toConsumableArray2["default"])(center))));
|
||||
}
|
||||
|
||||
// Provide the CLI
|
||||
_commander2.default.version(_package.version).description('Start a server to render Mapbox GL map requests to images.').option('-p, --port <n>', 'Server port', parseInt).option('-t, --tiles <mbtiles_path>', 'Directory containing local mbtiles files to render').parse(process.argv);
|
||||
if (Number.isNaN(center[0]) || Math.abs(center[0]) > 180) {
|
||||
return next(new _restifyErrors["default"].BadRequestError("Center longitude is outside world bounds (-180 to 180 deg): ".concat(center[0])));
|
||||
}
|
||||
|
||||
var _cli$port = _commander2.default.port,
|
||||
port = _cli$port === undefined ? 8000 : _cli$port,
|
||||
_cli$tiles = _commander2.default.tiles,
|
||||
tilePath = _cli$tiles === undefined ? null : _cli$tiles;
|
||||
if (Number.isNaN(center[1]) || Math.abs(center[1]) > 90) {
|
||||
return next(new _restifyErrors["default"].BadRequestError("Center latitude is outside world bounds (-90 to 90 deg): ".concat(center[1])));
|
||||
}
|
||||
}
|
||||
|
||||
if (zoom !== null) {
|
||||
zoom = parseFloat(zoom);
|
||||
|
||||
if (zoom < 0 || zoom > 22) {
|
||||
return next(new _restifyErrors["default"].BadRequestError("Zoom level is outside supported range (0-22): ".concat(zoom)));
|
||||
}
|
||||
}
|
||||
|
||||
if (ratio !== null) {
|
||||
ratio = parseInt(ratio);
|
||||
|
||||
if (!ratio || ratio < 1) {
|
||||
return next(new _restifyErrors["default"].BadRequestError("Ratio is outside supported range (>=1): ".concat(ratio)));
|
||||
}
|
||||
}
|
||||
|
||||
if (bounds !== null) {
|
||||
if (typeof bounds === 'string') {
|
||||
bounds = parseListToFloat(bounds);
|
||||
}
|
||||
|
||||
if (bounds.length !== 4) {
|
||||
return next(new _restifyErrors["default"].BadRequestError("Bounds must be west,south,east,north. Invalid value found: ".concat((0, _toConsumableArray2["default"])(bounds))));
|
||||
}
|
||||
|
||||
bounds.forEach(function (b) {
|
||||
if (Number.isNaN(b)) {
|
||||
return next(new _restifyErrors["default"].BadRequestError("Bounds must be west,south,east,north. Invalid value found: ".concat((0, _toConsumableArray2["default"])(bounds))));
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
if (!(center && zoom !== null || bounds)) {
|
||||
return next(new _restifyErrors["default"].BadRequestError('Either center and zoom OR bounds must be provided'));
|
||||
}
|
||||
|
||||
try {
|
||||
(0, _render["default"])(style, parseInt(width, 10), parseInt(height, 10), {
|
||||
zoom: zoom,
|
||||
center: center,
|
||||
bounds: bounds,
|
||||
tilePath: tilePath,
|
||||
ratio: ratio,
|
||||
token: token
|
||||
}).then(function (data, rejected) {
|
||||
if (rejected) {
|
||||
console.error('render request rejected', rejected);
|
||||
return next(new _restifyErrors["default"].InternalServerError({
|
||||
cause: rejected
|
||||
}, 'Error processing render request'));
|
||||
}
|
||||
|
||||
return response.sendRaw(200, data, {
|
||||
'content-type': 'image/png'
|
||||
});
|
||||
})["catch"](function (err) {
|
||||
console.error('Error processing render request', err);
|
||||
return next(new _restifyErrors["default"].InternalServerError({
|
||||
cause: err
|
||||
}, 'Error processing render request'));
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('Error processing render request', err);
|
||||
return next(new _restifyErrors["default"].InternalServerError({
|
||||
cause: err
|
||||
}, 'Error processing render request'));
|
||||
}
|
||||
|
||||
return null;
|
||||
}; // Provide the CLI
|
||||
|
||||
|
||||
var server = _restify2.default.createServer();
|
||||
server.use(_restify2.default.plugins.queryParser());
|
||||
server.use(_restify2.default.plugins.bodyParser());
|
||||
server.use(_nodeRestifyValidation2.default.validationPlugin({
|
||||
errorsAsArray: false,
|
||||
forbidUndefinedVariables: false,
|
||||
errorHandler: _restifyErrors2.default.BadRequestError
|
||||
_commander["default"].version(_package.version).description('Start a server to render Mapbox GL map requests to images.').option('-p, --port <n>', 'Server port', parseInt).option('-t, --tiles <mbtiles_path>', 'Directory containing local mbtiles files to render').parse(process.argv);
|
||||
|
||||
var _cli$port = _commander["default"].port,
|
||||
port = _cli$port === void 0 ? 8000 : _cli$port,
|
||||
_cli$tiles = _commander["default"].tiles,
|
||||
tilePath = _cli$tiles === void 0 ? null : _cli$tiles;
|
||||
|
||||
var server = _restify["default"].createServer();
|
||||
|
||||
server.use(_restify["default"].plugins.queryParser());
|
||||
server.use(_restify["default"].plugins.bodyParser());
|
||||
server.use(_nodeRestifyValidation["default"].validationPlugin({
|
||||
errorsAsArray: false,
|
||||
forbidUndefinedVariables: false,
|
||||
errorHandler: _restifyErrors["default"].BadRequestError
|
||||
}));
|
||||
|
||||
server.get({
|
||||
url: '/render',
|
||||
validation: {
|
||||
queries: PARAMS
|
||||
}
|
||||
url: '/render',
|
||||
validation: {
|
||||
queries: PARAMS
|
||||
}
|
||||
}, function (req, res, next) {
|
||||
return renderImage(req.query, res, next, tilePath);
|
||||
return renderImage(req.query, res, next, tilePath);
|
||||
});
|
||||
|
||||
server.post({
|
||||
url: '/render',
|
||||
validation: {
|
||||
content: PARAMS
|
||||
}
|
||||
url: '/render',
|
||||
validation: {
|
||||
content: PARAMS
|
||||
}
|
||||
}, function (req, res, next) {
|
||||
return renderImage(req.body, res, next, tilePath);
|
||||
return renderImage(req.body, res, next, tilePath);
|
||||
});
|
||||
server.get({
|
||||
url: '/'
|
||||
}, function (req, res) {
|
||||
var methods = ['GET', 'POST'];
|
||||
var routes = {};
|
||||
methods.forEach(function (method) {
|
||||
server.router.routes[method].forEach(function (_ref) {
|
||||
var url = _ref.spec.url;
|
||||
|
||||
server.get({ url: '/' }, function (req, res) {
|
||||
var methods = ['GET', 'POST'];
|
||||
var routes = {};
|
||||
methods.forEach(function (method) {
|
||||
server.router.routes[method].forEach(function (_ref) {
|
||||
var url = _ref.spec.url;
|
||||
if (!routes[url]) {
|
||||
routes[url] = [];
|
||||
}
|
||||
|
||||
if (!routes[url]) {
|
||||
routes[url] = [];
|
||||
}
|
||||
routes[url].push(method);
|
||||
});
|
||||
});
|
||||
res.send({
|
||||
routes: routes
|
||||
routes[url].push(method);
|
||||
});
|
||||
});
|
||||
res.send({
|
||||
routes: routes
|
||||
});
|
||||
});
|
||||
|
||||
if (tilePath !== null) {
|
||||
console.log('Using local mbtiles in: %j', tilePath);
|
||||
console.log('Using local mbtiles in: %j', tilePath);
|
||||
}
|
||||
|
||||
server.listen(port, function () {
|
||||
console.log('Mapbox GL static rendering server started and listening at %s', server.url);
|
||||
console.log('Mapbox GL static rendering server started and listening at %s', server.url);
|
||||
});
|
||||
10153
package-lock.json
generated
38
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mbgl-renderer",
|
||||
"version": "0.4.0",
|
||||
"version": "0.4.1",
|
||||
"description": "Static Map Renderer using Mapbox GL",
|
||||
"main": "dist/index.js",
|
||||
"license": "ISC",
|
||||
@ -17,30 +17,30 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@mapbox/geo-viewport": "^0.4.0",
|
||||
"@mapbox/mapbox-gl-native": "^4.1.0",
|
||||
"@mapbox/mbtiles": "^0.10.0",
|
||||
"commander": "^2.16.0",
|
||||
"@mapbox/mapbox-gl-native": "^4.2.0",
|
||||
"@mapbox/mbtiles": "^0.11.0",
|
||||
"commander": "^2.20.0",
|
||||
"node-restify-validation": "^1.3.0",
|
||||
"request": "^2.87.0",
|
||||
"restify": "6.x",
|
||||
"restify-errors": "^6.1.1",
|
||||
"sharp": "^0.21.3"
|
||||
"sharp": "^0.23.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-cli": "^6.26.0",
|
||||
"babel-core": "^6.26.3",
|
||||
"babel-eslint": "^8.2.6",
|
||||
"babel-jest": "^23.4.0",
|
||||
"babel-preset-es2015": "^6.24.1",
|
||||
"eslint": "4.19.1",
|
||||
"eslint-config-airbnb": "^17.0.0",
|
||||
"eslint-plugin-import": "^2.12.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.0.3",
|
||||
"eslint-plugin-react": "^7.9.1",
|
||||
"jest": "^23.4.1",
|
||||
"pixelmatch": "^4.0.2",
|
||||
"prettier-eslint": "^8.8.2",
|
||||
"regenerator-runtime": "^0.12.0"
|
||||
"@babel/cli": "^7.5.5",
|
||||
"@babel/core": "^7.5.5",
|
||||
"@babel/plugin-transform-runtime": "^7.5.5",
|
||||
"@babel/preset-env": "^7.5.5",
|
||||
"babel-eslint": "^10.0.2",
|
||||
"babel-preset-jest": "^24.9.0",
|
||||
"eslint": "^6.2.1",
|
||||
"eslint-config-airbnb-base": "^14.0.0",
|
||||
"eslint-plugin-import": "^2.18.2",
|
||||
"eslint-plugin-jsx-a11y": "^6.2.3",
|
||||
"fsevents": "^2.0.7",
|
||||
"jest": "^24.9.0",
|
||||
"pixelmatch": "^5.0.2",
|
||||
"prettier-eslint": "^9.0.0"
|
||||
},
|
||||
"repository": "https://github.com/consbio/mbgl-renderer"
|
||||
}
|
||||
|
||||
BIN
tests/fixtures/expected-bounds.png
vendored
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 67 KiB |
BIN
tests/fixtures/expected-mbtiles-source-vector.png
vendored
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 74 KiB |
BIN
tests/fixtures/expected-mbtiles-tiles-vector.png
vendored
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 26 KiB |
BIN
tests/fixtures/expected-mbtiles-tiles.png
vendored
|
Before Width: | Height: | Size: 188 KiB After Width: | Height: | Size: 189 KiB |
@ -9,166 +9,158 @@ import mbtilesSourceVectorStyle from './fixtures/example-style-mbtiles-source-ve
|
||||
import mbtilesTilesStyle from './fixtures/example-style-mbtiles-tiles.json'
|
||||
import mbtilesTilesVectorStyle from './fixtures/example-style-mbtiles-tiles-vector.json'
|
||||
import mapboxSourceStyle from './fixtures/example-style-mapbox-source.json'
|
||||
/**
|
||||
* Uses `pixelmatch` to calculate the number of pixels that are different between
|
||||
* the PNG data produced by `render` and the data in the expected PNG file.
|
||||
*
|
||||
* @param {Buffer} pngData - buffer of PNG data produced by render function
|
||||
* @param {String} expectedPath - path to PNG file of expected data from test suite
|
||||
*
|
||||
* Returns {Number} - count of pixels that are different between the 2 images.
|
||||
*/
|
||||
async function imageDiff(pngData, expectedPath) {
|
||||
const pngImage = await sharp(pngData)
|
||||
const { width, height } = await pngImage.metadata()
|
||||
|
||||
test('creates correct image width and height', () => render(style, 512, 256, {
|
||||
zoom: 10,
|
||||
center: [-79.86, 32.68]
|
||||
}).then((data) => {
|
||||
// feed it back through sharp to verify that we got an image
|
||||
sharp(data).metadata((_, { format, width, height }) => {
|
||||
expect(format).toBe('png')
|
||||
expect(width).toBe(512)
|
||||
expect(height).toBe(256)
|
||||
// Convert the pixels to raw byte buffer
|
||||
const rawData = await pngImage.raw().toBuffer()
|
||||
|
||||
// read the expected data and convert to raw byte buffer
|
||||
const expected = await sharp(expectedPath)
|
||||
.raw()
|
||||
.toBuffer()
|
||||
|
||||
return pixelmatch(rawData, expected, null, width, height)
|
||||
}
|
||||
|
||||
test('creates correct image width and height', async () => {
|
||||
const data = await render(style, 512, 256, {
|
||||
zoom: 10,
|
||||
center: [-79.86, 32.68]
|
||||
})
|
||||
|
||||
// feed it back through sharp to verify that we got an image
|
||||
const { format, width, height } = await sharp(data).metadata()
|
||||
expect(format).toBe('png')
|
||||
expect(width).toBe(512)
|
||||
expect(height).toBe(256)
|
||||
})
|
||||
|
||||
test('outputs correct image', async () => {
|
||||
const data = await render(style, 512, 256, {
|
||||
zoom: 10,
|
||||
center: [-79.86, 32.68]
|
||||
})
|
||||
}))
|
||||
|
||||
test('outputs correct image', () => render(style, 512, 256, {
|
||||
zoom: 10,
|
||||
center: [-79.86, 32.68]
|
||||
}).then((data) => {
|
||||
const expectedPath = path.join(__dirname, './fixtures/expected.png')
|
||||
// to write out known good image:
|
||||
// fs.writeFileSync(expectedPath, data)
|
||||
|
||||
sharp(expectedPath).toBuffer((_, expected) => {
|
||||
const diffPixels = pixelmatch(data, expected, null, 512, 256)
|
||||
expect(diffPixels).toBe(0)
|
||||
})
|
||||
}))
|
||||
const diffPixels = await imageDiff(data, expectedPath)
|
||||
expect(diffPixels).toBe(0)
|
||||
})
|
||||
|
||||
test('creates image using bounds', async () => {
|
||||
const data = await render(style, 512, 256, {
|
||||
zoom: null,
|
||||
center: null,
|
||||
bounds: [-80.23, 32.678, -79.73, 32.891]
|
||||
})
|
||||
|
||||
test('creates image using bounds', () => render(style, 512, 256, {
|
||||
zoom: null,
|
||||
center: null,
|
||||
bounds: [-80.23, 32.678, -79.73, 32.891]
|
||||
}).then((data) => {
|
||||
// feed it back through sharp to verify that we got an image
|
||||
const expectedPath = path.join(__dirname, './fixtures/expected-bounds.png')
|
||||
// to write out known good image:
|
||||
// fs.writeFileSync(expectedPath, data)
|
||||
|
||||
sharp(expectedPath).toBuffer((_, expected) => {
|
||||
const diffPixels = pixelmatch(data, expected, null, 512, 256)
|
||||
expect(diffPixels).toBe(0)
|
||||
const diffPixels = await imageDiff(data, expectedPath)
|
||||
expect(diffPixels).toBe(0)
|
||||
})
|
||||
|
||||
test('resolves local mbtiles from raster source', async () => {
|
||||
const data = await render(mbtilesSourceStyle, 512, 512, {
|
||||
zoom: 1,
|
||||
center: [0, 0],
|
||||
tilePath: path.join(__dirname, './fixtures/')
|
||||
})
|
||||
}))
|
||||
|
||||
test('resolves local mbtiles from raster source', () => render(mbtilesSourceStyle, 512, 512, {
|
||||
zoom: 1,
|
||||
center: [0, 0],
|
||||
tilePath: path.join(__dirname, './fixtures/')
|
||||
}).then((data) => {
|
||||
// feed it back through sharp to verify that we got an image
|
||||
sharp(data)
|
||||
.metadata((_, { format, width, height }) => {
|
||||
expect(format).toBe('png')
|
||||
expect(width).toBe(512)
|
||||
expect(height).toBe(512)
|
||||
})
|
||||
.stats((_, { channels }) => {
|
||||
expect(channels[0].squaresSum).toBeGreaterThan(0)
|
||||
})
|
||||
const expectedPath = path.join(__dirname, './fixtures/expected-mbtiles-source.png')
|
||||
// to write out known good image:
|
||||
// fs.writeFileSync(expectedPath, data)
|
||||
|
||||
// to write out known good image:
|
||||
// fs.writeFileSync(path.join(__dirname, './fixtures/expected-mbtiles-source.png'), data)
|
||||
}))
|
||||
const diffPixels = await imageDiff(data, expectedPath)
|
||||
expect(diffPixels).toBe(0)
|
||||
})
|
||||
|
||||
test('resolves local mbtiles from vector source', () => render(mbtilesSourceVectorStyle, 512, 512, {
|
||||
zoom: 0,
|
||||
center: [0, 0],
|
||||
tilePath: path.join(__dirname, './fixtures/')
|
||||
}).then((data) => {
|
||||
// feed it back through sharp to verify that we got an image
|
||||
sharp(data)
|
||||
.metadata((_, { format, width, height }) => {
|
||||
expect(format).toBe('png')
|
||||
expect(width).toBe(512)
|
||||
expect(height).toBe(512)
|
||||
})
|
||||
.stats((_, { channels }) => {
|
||||
expect(channels[0].squaresSum).toBeGreaterThan(0)
|
||||
})
|
||||
test('resolves local mbtiles from vector source', async () => {
|
||||
const data = await render(mbtilesSourceVectorStyle, 512, 512, {
|
||||
zoom: 0,
|
||||
center: [0, 0],
|
||||
tilePath: path.join(__dirname, './fixtures/')
|
||||
})
|
||||
|
||||
// to write out known good image:
|
||||
// fs.writeFileSync(path.join(__dirname, './fixtures/expected-mbtiles-source-vector.png'), data)
|
||||
}))
|
||||
const expectedPath = path.join(__dirname, './fixtures/expected-mbtiles-source-vector.png')
|
||||
// to write out known good image:
|
||||
// fs.writeFileSync(expectedPath, data)
|
||||
|
||||
test('resolves local mbtiles from tiles', () => render(mbtilesTilesStyle, 512, 512, {
|
||||
zoom: 1,
|
||||
center: [0, 0],
|
||||
tilePath: path.join(__dirname, './fixtures/')
|
||||
}).then((data) => {
|
||||
// feed it back through sharp to verify that we got an image
|
||||
sharp(data)
|
||||
.metadata((_, { format, width, height }) => {
|
||||
expect(format).toBe('png')
|
||||
expect(width).toBe(512)
|
||||
expect(height).toBe(512)
|
||||
})
|
||||
.stats((_, { channels }) => {
|
||||
expect(channels[0].squaresSum).toBeGreaterThan(0)
|
||||
})
|
||||
const diffPixels = await imageDiff(data, expectedPath)
|
||||
expect(diffPixels).toBe(0)
|
||||
})
|
||||
|
||||
// to write out known good image:
|
||||
// fs.writeFileSync(path.join(__dirname, './fixtures/expected-mbtiles-tiles.png'), data)
|
||||
}))
|
||||
test('resolves local mbtiles from tiles', async () => {
|
||||
const data = await render(mbtilesTilesStyle, 512, 512, {
|
||||
zoom: 1,
|
||||
center: [0, 0],
|
||||
tilePath: path.join(__dirname, './fixtures/')
|
||||
})
|
||||
const expectedPath = path.join(__dirname, './fixtures/expected-mbtiles-tiles.png')
|
||||
// to write out known good image:
|
||||
// fs.writeFileSync(expectedPath, data)
|
||||
|
||||
test('resolves local mbtiles from vector tiles', () => render(mbtilesTilesVectorStyle, 512, 512, {
|
||||
zoom: 0,
|
||||
center: [0, 0],
|
||||
tilePath: path.join(__dirname, './fixtures/')
|
||||
}).then((data) => {
|
||||
// feed it back through sharp to verify that we got an image
|
||||
sharp(data)
|
||||
.metadata((_, { format, width, height }) => {
|
||||
expect(format).toBe('png')
|
||||
expect(width).toBe(512)
|
||||
expect(height).toBe(512)
|
||||
})
|
||||
.stats((_, { channels }) => {
|
||||
expect(channels[0].squaresSum).toBeGreaterThan(0)
|
||||
})
|
||||
const diffPixels = await imageDiff(data, expectedPath)
|
||||
expect(diffPixels).toBe(0)
|
||||
})
|
||||
|
||||
// to write out known good image:
|
||||
// fs.writeFileSync(path.join(__dirname, './fixtures/expected-mbtiles-tiles-vector.png'), data)
|
||||
}))
|
||||
test('resolves local mbtiles from vector tiles', async () => {
|
||||
const data = await render(mbtilesTilesVectorStyle, 512, 512, {
|
||||
zoom: 0,
|
||||
center: [0, 0],
|
||||
tilePath: path.join(__dirname, './fixtures/')
|
||||
})
|
||||
|
||||
test('resolves from mapbox source', () => render(mapboxSourceStyle, 512, 512, {
|
||||
zoom: 0,
|
||||
center: [0, 0],
|
||||
token: 'pk.eyJ1IjoiYmN3YXJkIiwiYSI6InJ5NzUxQzAifQ.CVyzbyOpnStfYUQ_6r8AgQ' // mapbox docs token
|
||||
}).then((data) => {
|
||||
// feed it back through sharp to verify that we got an image
|
||||
sharp(data)
|
||||
.metadata((_, { format, width, height }) => {
|
||||
expect(format).toBe('png')
|
||||
expect(width).toBe(512)
|
||||
expect(height).toBe(512)
|
||||
})
|
||||
.stats((_, { channels }) => {
|
||||
expect(channels[0].squaresSum).toBeGreaterThan(0)
|
||||
})
|
||||
const expectedPath = path.join(__dirname, './fixtures/expected-mbtiles-tiles-vector.png')
|
||||
// to write out known good image:
|
||||
// fs.writeFileSync(expectedPath, data)
|
||||
|
||||
// to write out known good image:
|
||||
// fs.writeFileSync(path.join(__dirname, './fixtures/expected-mapbox-source.png'), data)
|
||||
}))
|
||||
const diffPixels = await imageDiff(data, expectedPath)
|
||||
expect(diffPixels).toBe(0)
|
||||
})
|
||||
|
||||
test('resolves from mapbox source with ratio', () => render(mapboxSourceStyle, 512, 512, {
|
||||
zoom: 0,
|
||||
ratio: 2,
|
||||
center: [0, 0],
|
||||
token: 'pk.eyJ1IjoiYmN3YXJkIiwiYSI6InJ5NzUxQzAifQ.CVyzbyOpnStfYUQ_6r8AgQ' // mapbox docs token
|
||||
}).then((data) => {
|
||||
// feed it back through sharp to verify that we got an image
|
||||
sharp(data)
|
||||
.metadata((_, { format, width, height }) => {
|
||||
expect(format).toBe('png')
|
||||
expect(width).toBe(1024)
|
||||
expect(height).toBe(1024)
|
||||
})
|
||||
.stats((_, { channels }) => {
|
||||
expect(channels[0].squaresSum).toBeGreaterThan(0)
|
||||
})
|
||||
test('resolves from mapbox source', async () => {
|
||||
const data = await render(mapboxSourceStyle, 512, 512, {
|
||||
zoom: 0,
|
||||
center: [0, 0],
|
||||
token: 'pk.eyJ1IjoiYmN3YXJkIiwiYSI6InJ5NzUxQzAifQ.CVyzbyOpnStfYUQ_6r8AgQ' // mapbox docs token
|
||||
})
|
||||
|
||||
// to write out known good image:
|
||||
// fs.writeFileSync(path.join(__dirname, './fixtures/expected-mapbox-source@2x.png'), data)
|
||||
}))
|
||||
const expectedPath = path.join(__dirname, './fixtures/expected-mapbox-source.png')
|
||||
// to write out known good image:
|
||||
// fs.writeFileSync(expectedPath, data)
|
||||
|
||||
const diffPixels = await imageDiff(data, expectedPath)
|
||||
expect(diffPixels).toBe(0)
|
||||
})
|
||||
|
||||
test('resolves from mapbox source with ratio', async () => {
|
||||
const data = await render(mapboxSourceStyle, 512, 512, {
|
||||
zoom: 0,
|
||||
ratio: 2,
|
||||
center: [0, 0],
|
||||
token: 'pk.eyJ1IjoiYmN3YXJkIiwiYSI6InJ5NzUxQzAifQ.CVyzbyOpnStfYUQ_6r8AgQ' // mapbox docs token
|
||||
})
|
||||
|
||||
const expectedPath = path.join(__dirname, './fixtures/expected-mapbox-source@2x.png')
|
||||
// to write out known good image:
|
||||
// fs.writeFileSync(expectedPath, data)
|
||||
|
||||
const diffPixels = await imageDiff(data, expectedPath)
|
||||
expect(diffPixels).toBe(0)
|
||||
})
|
||||
|
||||