Upgraded several dependencies, resolved #30

This commit is contained in:
Brendan Ward 2019-08-21 15:43:26 -07:00
parent 2715397c8d
commit 4eb7529f1b
14 changed files with 7710 additions and 4193 deletions

View File

@ -1,5 +1,11 @@
{
"presets": [
"es2015"
]
"presets": ["@babel/preset-env"],
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"regenerator": true
}
]
]
}

View File

@ -1,5 +1,5 @@
{
"extends": ["airbnb"],
"extends": ["airbnb-base"],
"env": {
"es6": true,
"node": true,

View File

@ -209,6 +209,12 @@ http://localhost:8080/render?height=1024&width=1024&center=-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
View File

@ -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
View File

@ -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
View File

@ -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
View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -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"
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 188 KiB

After

Width:  |  Height:  |  Size: 189 KiB

View File

@ -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)
})