style: fix lint issues in code 🕺 (#626)
* style: fix lint issues in code 🕺
Signed-off-by: Vinayak Kulkarni <19776877+vinayakkulkarni@users.noreply.github.com>
* style: lint fix all files
Signed-off-by: Vinayak Kulkarni <19776877+vinayakkulkarni@users.noreply.github.com>
* chore: add `keywords` for better reach
Signed-off-by: Vinayak Kulkarni <19776877+vinayakkulkarni@users.noreply.github.com>
* feat: add `husky` & `commitlint`
Signed-off-by: Vinayak Kulkarni <19776877+vinayakkulkarni@users.noreply.github.com>
* chore: ignore `public` directory
Signed-off-by: Vinayak Kulkarni <19776877+vinayakkulkarni@users.noreply.github.com>
* revert: do not lint `public` directory
Signed-off-by: Vinayak Kulkarni <19776877+vinayakkulkarni@users.noreply.github.com>
* style: fix issues with lint
Signed-off-by: Vinayak Kulkarni <19776877+vinayakkulkarni@users.noreply.github.com>
* feat: add eslint config
Signed-off-by: Vinayak Kulkarni <19776877+vinayakkulkarni@users.noreply.github.com>
* feat: add lint-staged
Signed-off-by: Vinayak Kulkarni <19776877+vinayakkulkarni@users.noreply.github.com>
* style: lint fix all file(s)
Signed-off-by: Vinayak Kulkarni <19776877+vinayakkulkarni@users.noreply.github.com>
* fix: ignore rules for light version
Signed-off-by: Vinayak Kulkarni <19776877+vinayakkulkarni@users.noreply.github.com>
* fix: remove unnecessary space
Signed-off-by: Vinayak Kulkarni <19776877+vinayakkulkarni@users.noreply.github.com>
* chore(deps): update lockfile
Signed-off-by: Vinayak Kulkarni <19776877+vinayakkulkarni@users.noreply.github.com>
* style: autofix linting issue(s)
Signed-off-by: Vinayak Kulkarni <19776877+vinayakkulkarni@users.noreply.github.com>
Signed-off-by: Vinayak Kulkarni <19776877+vinayakkulkarni@users.noreply.github.com>
This commit is contained in:
parent
50201f0a99
commit
9b64093c42
24 changed files with 8084 additions and 1264 deletions
32
.eslintrc.cjs
Normal file
32
.eslintrc.cjs
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
env: {
|
||||||
|
browser: true,
|
||||||
|
node: true,
|
||||||
|
es6: true,
|
||||||
|
},
|
||||||
|
parserOptions: {
|
||||||
|
parser: '@typescript-eslint/parser',
|
||||||
|
ecmaVersion: 2020,
|
||||||
|
sourceType: 'module',
|
||||||
|
lib: ['es2020'],
|
||||||
|
ecmaFeatures: {
|
||||||
|
jsx: true,
|
||||||
|
tsx: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: ['prettier', 'jsdoc', 'security'],
|
||||||
|
extends: [
|
||||||
|
'prettier',
|
||||||
|
'plugin:@typescript-eslint/recommended',
|
||||||
|
'plugin:@typescript-eslint/eslint-recommended',
|
||||||
|
'plugin:prettier/recommended',
|
||||||
|
'plugin:jsdoc/recommended',
|
||||||
|
'plugin:security/recommended',
|
||||||
|
],
|
||||||
|
// add your custom rules here
|
||||||
|
rules: {
|
||||||
|
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
|
||||||
|
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
|
||||||
|
},
|
||||||
|
};
|
21
.husky/commit-msg
Executable file
21
.husky/commit-msg
Executable file
|
@ -0,0 +1,21 @@
|
||||||
|
#!/bin/sh
|
||||||
|
. "$(dirname "$0")/_/husky.sh"
|
||||||
|
|
||||||
|
NAME=$(git config user.name)
|
||||||
|
EMAIL=$(git config user.email)
|
||||||
|
|
||||||
|
if [ -z "$NAME" ]; then
|
||||||
|
echo "empty git config user.name"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$EMAIL" ]; then
|
||||||
|
echo "empty git config user.email"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
git interpret-trailers --if-exists doNothing --trailer \
|
||||||
|
"Signed-off-by: $NAME <$EMAIL>" \
|
||||||
|
--in-place "$1"
|
||||||
|
|
||||||
|
npm exec --no -- commitlint --edit $1
|
4
.husky/pre-push
Executable file
4
.husky/pre-push
Executable file
|
@ -0,0 +1,4 @@
|
||||||
|
#!/bin/sh
|
||||||
|
. "$(dirname "$0")/_/husky.sh"
|
||||||
|
|
||||||
|
npm exec --no -- lint-staged --no-stash
|
3
commitlint.config.cjs
Normal file
3
commitlint.config.cjs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
module.exports = {
|
||||||
|
extends: ['@commitlint/config-conventional'],
|
||||||
|
};
|
4
lint-staged.config.cjs
Normal file
4
lint-staged.config.cjs
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
module.exports = {
|
||||||
|
'*.{js,ts}': 'npm run lint:js',
|
||||||
|
'*.{yml}': 'npm run lint:yml',
|
||||||
|
};
|
7092
package-lock.json
generated
7092
package-lock.json
generated
File diff suppressed because it is too large
Load diff
54
package.json
54
package.json
|
@ -5,17 +5,17 @@
|
||||||
"main": "src/main.js",
|
"main": "src/main.js",
|
||||||
"bin": "src/main.js",
|
"bin": "src/main.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "git+https://github.com/maptiler/tileserver-gl.git"
|
|
||||||
},
|
|
||||||
"license": "BSD-2-Clause",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=14.15.0 <17"
|
|
||||||
},
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "mocha test/**.js --timeout 10000",
|
"test": "mocha test/**.js --timeout 10000",
|
||||||
"docker": "docker build -f Dockerfile . && docker run --rm -i -p 8080:80 $(docker build -q .)"
|
"lint:yml": "yamllint --schema=CORE_SCHEMA *.{yml,yaml}",
|
||||||
|
"lint:js": "npm run lint:eslint && npm run lint:prettier",
|
||||||
|
"lint:js:fix": "npm run lint:eslint:fix && npm run lint:prettier:fix",
|
||||||
|
"lint:eslint": "eslint \"{,!(node_modules|dist|static|public)/**/}*.{js,ts,cjs,mjs}\" --ignore-path .gitignore",
|
||||||
|
"lint:eslint:fix": "eslint --fix \"{,!(node_modules|dist|static|public)/**/}*.{js,ts,cjs,mjs}\" --ignore-path .gitignore",
|
||||||
|
"lint:prettier": "prettier --check \"{,!(node_modules|dist|static|public)/**/}*.{js,ts,cjs,mjs,json}\" --ignore-path .gitignore",
|
||||||
|
"lint:prettier:fix": "prettier --write \"{,!(node_modules|dist|static|public)/**/}*.{js,ts,cjs,mjs,json}\" --ignore-path .gitignore",
|
||||||
|
"docker": "docker build -f Dockerfile . && docker run --rm -i -p 8080:80 $(docker build -q .)",
|
||||||
|
"prepare": "husky install"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mapbox/glyph-pbf-composite": "0.0.3",
|
"@mapbox/glyph-pbf-composite": "0.0.3",
|
||||||
|
@ -43,8 +43,40 @@
|
||||||
"sanitize-filename": "1.6.3"
|
"sanitize-filename": "1.6.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@commitlint/cli": "^17.1.2",
|
||||||
|
"@commitlint/config-conventional": "^17.1.0",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^5.38.0",
|
||||||
|
"@typescript-eslint/parser": "^5.38.0",
|
||||||
"chai": "4.3.6",
|
"chai": "4.3.6",
|
||||||
|
"eslint": "^8.24.0",
|
||||||
|
"eslint-config-prettier": "^8.5.0",
|
||||||
|
"eslint-plugin-jsdoc": "^39.3.6",
|
||||||
|
"eslint-plugin-prettier": "^4.2.1",
|
||||||
|
"eslint-plugin-security": "^1.5.0",
|
||||||
|
"husky": "^8.0.1",
|
||||||
|
"lint-staged": "^13.0.3",
|
||||||
"mocha": "^10.0.0",
|
"mocha": "^10.0.0",
|
||||||
"supertest": "^6.2.4"
|
"prettier": "^2.7.1",
|
||||||
}
|
"should": "^13.2.3",
|
||||||
|
"supertest": "^6.2.4",
|
||||||
|
"yaml-lint": "^1.7.0"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"maptiler",
|
||||||
|
"tileserver-gl",
|
||||||
|
"maplibre-gl",
|
||||||
|
"tileserver"
|
||||||
|
],
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.15.0 <17"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"url": "git+https://github.com/maptiler/tileserver-gl.git",
|
||||||
|
"type": "git"
|
||||||
|
},
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/maptiler/tileserver-gl/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/maptiler/tileserver-gl#readme"
|
||||||
}
|
}
|
||||||
|
|
13
prettier.config.cjs
Normal file
13
prettier.config.cjs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
module.exports = {
|
||||||
|
$schema: 'http://json.schemastore.org/prettierrc',
|
||||||
|
semi: true,
|
||||||
|
arrowParens: 'always',
|
||||||
|
singleQuote: true,
|
||||||
|
trailingComma: 'all',
|
||||||
|
bracketSpacing: true,
|
||||||
|
htmlWhitespaceSensitivity: 'css',
|
||||||
|
insertPragma: false,
|
||||||
|
tabWidth: 2,
|
||||||
|
useTabs: false,
|
||||||
|
endOfLine: 'lf',
|
||||||
|
};
|
22
publish.js
22
publish.js
|
@ -12,10 +12,13 @@
|
||||||
|
|
||||||
// SYNC THE `light` FOLDER
|
// SYNC THE `light` FOLDER
|
||||||
|
|
||||||
import child_process from 'child_process'
|
import child_process from 'child_process';
|
||||||
child_process.execSync('rsync -av --exclude="light" --exclude=".git" --exclude="node_modules" --delete . light', {
|
child_process.execSync(
|
||||||
stdio: 'inherit'
|
'rsync -av --exclude="light" --exclude=".git" --exclude="node_modules" --delete . light',
|
||||||
});
|
{
|
||||||
|
stdio: 'inherit',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
// PATCH `package.json`
|
// PATCH `package.json`
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
|
@ -23,10 +26,13 @@ import path from 'path';
|
||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from 'url';
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
const __dirname = path.dirname(__filename);
|
const __dirname = path.dirname(__filename);
|
||||||
const packageJson = JSON.parse(fs.readFileSync(__dirname + '/package.json', 'utf8'))
|
const packageJson = JSON.parse(
|
||||||
|
fs.readFileSync(__dirname + '/package.json', 'utf8'),
|
||||||
|
);
|
||||||
|
|
||||||
packageJson.name += '-light';
|
packageJson.name += '-light';
|
||||||
packageJson.description = 'Map tile server for JSON GL styles - serving vector tiles';
|
packageJson.description =
|
||||||
|
'Map tile server for JSON GL styles - serving vector tiles';
|
||||||
delete packageJson.dependencies['canvas'];
|
delete packageJson.dependencies['canvas'];
|
||||||
delete packageJson.dependencies['@maplibre/maplibre-gl-native'];
|
delete packageJson.dependencies['@maplibre/maplibre-gl-native'];
|
||||||
delete packageJson.dependencies['sharp'];
|
delete packageJson.dependencies['sharp'];
|
||||||
|
@ -51,10 +57,10 @@ if (process.argv.length > 2 && process.argv[2] == '--no-publish') {
|
||||||
|
|
||||||
// tileserver-gl
|
// tileserver-gl
|
||||||
child_process.execSync('npm publish . --access public', {
|
child_process.execSync('npm publish . --access public', {
|
||||||
stdio: 'inherit'
|
stdio: 'inherit',
|
||||||
});
|
});
|
||||||
|
|
||||||
// tileserver-gl-light
|
// tileserver-gl-light
|
||||||
child_process.execSync('npm publish ./light --access public', {
|
child_process.execSync('npm publish ./light --access public', {
|
||||||
stdio: 'inherit'
|
stdio: 'inherit',
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,7 +2,7 @@ import * as http from 'http';
|
||||||
var options = {
|
var options = {
|
||||||
timeout: 2000,
|
timeout: 2000,
|
||||||
};
|
};
|
||||||
var url = "http://localhost:80/health";
|
var url = 'http://localhost:80/health';
|
||||||
var request = http.request(url, options, (res) => {
|
var request = http.request(url, options, (res) => {
|
||||||
console.log(`STATUS: ${res.statusCode}`);
|
console.log(`STATUS: ${res.statusCode}`);
|
||||||
if (res.statusCode == 200) {
|
if (res.statusCode == 200) {
|
||||||
|
@ -11,8 +11,8 @@ var request = http.request(url, options, (res) => {
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
request.on("error", function (err) {
|
request.on('error', function (err) {
|
||||||
console.log("ERROR");
|
console.log('ERROR');
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
});
|
});
|
||||||
request.end();
|
request.end();
|
||||||
|
|
114
src/main.js
114
src/main.js
|
@ -12,7 +12,9 @@ import MBTiles from '@mapbox/mbtiles';
|
||||||
|
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
const __dirname = path.dirname(__filename);
|
const __dirname = path.dirname(__filename);
|
||||||
const packageJson = JSON.parse(fs.readFileSync(__dirname + '/../package.json', 'utf8'));
|
const packageJson = JSON.parse(
|
||||||
|
fs.readFileSync(__dirname + '/../package.json', 'utf8'),
|
||||||
|
);
|
||||||
|
|
||||||
const args = process.argv;
|
const args = process.argv;
|
||||||
if (args.length >= 3 && args[2][0] !== '-') {
|
if (args.length >= 3 && args[2][0] !== '-') {
|
||||||
|
@ -26,51 +28,28 @@ program
|
||||||
.option(
|
.option(
|
||||||
'--mbtiles <file>',
|
'--mbtiles <file>',
|
||||||
'MBTiles file (uses demo configuration);\n' +
|
'MBTiles file (uses demo configuration);\n' +
|
||||||
'\t ignored if the configuration file is also specified'
|
'\t ignored if the configuration file is also specified',
|
||||||
)
|
)
|
||||||
.option(
|
.option(
|
||||||
'-c, --config <file>',
|
'-c, --config <file>',
|
||||||
'Configuration file [config.json]',
|
'Configuration file [config.json]',
|
||||||
'config.json'
|
'config.json',
|
||||||
)
|
|
||||||
.option(
|
|
||||||
'-b, --bind <address>',
|
|
||||||
'Bind address'
|
|
||||||
)
|
|
||||||
.option(
|
|
||||||
'-p, --port <port>',
|
|
||||||
'Port [8080]',
|
|
||||||
8080,
|
|
||||||
parseInt
|
|
||||||
)
|
|
||||||
.option(
|
|
||||||
'-C|--no-cors',
|
|
||||||
'Disable Cross-origin resource sharing headers'
|
|
||||||
)
|
)
|
||||||
|
.option('-b, --bind <address>', 'Bind address')
|
||||||
|
.option('-p, --port <port>', 'Port [8080]', 8080, parseInt)
|
||||||
|
.option('-C|--no-cors', 'Disable Cross-origin resource sharing headers')
|
||||||
.option(
|
.option(
|
||||||
'-u|--public_url <url>',
|
'-u|--public_url <url>',
|
||||||
'Enable exposing the server on subpaths, not necessarily the root of the domain'
|
'Enable exposing the server on subpaths, not necessarily the root of the domain',
|
||||||
)
|
|
||||||
.option(
|
|
||||||
'-V, --verbose',
|
|
||||||
'More verbose output'
|
|
||||||
)
|
|
||||||
.option(
|
|
||||||
'-s, --silent',
|
|
||||||
'Less verbose output'
|
|
||||||
)
|
|
||||||
.option(
|
|
||||||
'-l|--log_file <file>',
|
|
||||||
'output log file (defaults to standard out)'
|
|
||||||
)
|
)
|
||||||
|
.option('-V, --verbose', 'More verbose output')
|
||||||
|
.option('-s, --silent', 'Less verbose output')
|
||||||
|
.option('-l|--log_file <file>', 'output log file (defaults to standard out)')
|
||||||
.option(
|
.option(
|
||||||
'-f|--log_format <format>',
|
'-f|--log_format <format>',
|
||||||
'define the log format: https://github.com/expressjs/morgan#morganformat-options'
|
'define the log format: https://github.com/expressjs/morgan#morganformat-options',
|
||||||
)
|
|
||||||
.version(
|
|
||||||
packageJson.version,
|
|
||||||
'-v, --version'
|
|
||||||
)
|
)
|
||||||
|
.version(packageJson.version, '-v, --version');
|
||||||
program.parse(process.argv);
|
program.parse(process.argv);
|
||||||
const opts = program.opts();
|
const opts = program.opts();
|
||||||
|
|
||||||
|
@ -91,14 +70,16 @@ const startServer = (configPath, config) => {
|
||||||
silent: opts.silent,
|
silent: opts.silent,
|
||||||
logFile: opts.log_file,
|
logFile: opts.log_file,
|
||||||
logFormat: opts.log_format,
|
logFormat: opts.log_format,
|
||||||
publicUrl: publicUrl
|
publicUrl: publicUrl,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const startWithMBTiles = (mbtilesFile) => {
|
const startWithMBTiles = (mbtilesFile) => {
|
||||||
console.log(`[INFO] Automatically creating config file for ${mbtilesFile}`);
|
console.log(`[INFO] Automatically creating config file for ${mbtilesFile}`);
|
||||||
console.log(`[INFO] Only a basic preview style will be used.`);
|
console.log(`[INFO] Only a basic preview style will be used.`);
|
||||||
console.log(`[INFO] See documentation to learn how to create config.json file.`);
|
console.log(
|
||||||
|
`[INFO] See documentation to learn how to create config.json file.`,
|
||||||
|
);
|
||||||
|
|
||||||
mbtilesFile = path.resolve(process.cwd(), mbtilesFile);
|
mbtilesFile = path.resolve(process.cwd(), mbtilesFile);
|
||||||
|
|
||||||
|
@ -117,53 +98,63 @@ const startWithMBTiles = (mbtilesFile) => {
|
||||||
instance.getInfo((err, info) => {
|
instance.getInfo((err, info) => {
|
||||||
if (err || !info) {
|
if (err || !info) {
|
||||||
console.log('ERROR: Metadata missing in the MBTiles.');
|
console.log('ERROR: Metadata missing in the MBTiles.');
|
||||||
console.log(` Make sure ${path.basename(mbtilesFile)} is valid MBTiles.`);
|
console.log(
|
||||||
|
`Make sure ${path.basename(mbtilesFile)} is valid MBTiles.`,
|
||||||
|
);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
const bounds = info.bounds;
|
const bounds = info.bounds;
|
||||||
|
|
||||||
const styleDir = path.resolve(__dirname, '../node_modules/tileserver-gl-styles/');
|
const styleDir = path.resolve(
|
||||||
|
__dirname,
|
||||||
|
'../node_modules/tileserver-gl-styles/',
|
||||||
|
);
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
'options': {
|
options: {
|
||||||
'paths': {
|
paths: {
|
||||||
'root': styleDir,
|
root: styleDir,
|
||||||
'fonts': 'fonts',
|
fonts: 'fonts',
|
||||||
'styles': 'styles',
|
styles: 'styles',
|
||||||
'mbtiles': path.dirname(mbtilesFile)
|
mbtiles: path.dirname(mbtilesFile),
|
||||||
}
|
|
||||||
},
|
},
|
||||||
'styles': {},
|
},
|
||||||
'data': {}
|
styles: {},
|
||||||
|
data: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (info.format === 'pbf' &&
|
if (
|
||||||
info.name.toLowerCase().indexOf('openmaptiles') > -1) {
|
info.format === 'pbf' &&
|
||||||
|
info.name.toLowerCase().indexOf('openmaptiles') > -1
|
||||||
|
) {
|
||||||
config['data'][`v3`] = {
|
config['data'][`v3`] = {
|
||||||
'mbtiles': path.basename(mbtilesFile)
|
mbtiles: path.basename(mbtilesFile),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const styles = fs.readdirSync(path.resolve(styleDir, 'styles'));
|
const styles = fs.readdirSync(path.resolve(styleDir, 'styles'));
|
||||||
for (const styleName of styles) {
|
for (const styleName of styles) {
|
||||||
const styleFileRel = styleName + '/style.json';
|
const styleFileRel = styleName + '/style.json';
|
||||||
const styleFile = path.resolve(styleDir, 'styles', styleFileRel);
|
const styleFile = path.resolve(styleDir, 'styles', styleFileRel);
|
||||||
if (fs.existsSync(styleFile)) {
|
if (fs.existsSync(styleFile)) {
|
||||||
config['styles'][styleName] = {
|
config['styles'][styleName] = {
|
||||||
'style': styleFileRel,
|
style: styleFileRel,
|
||||||
'tilejson': {
|
tilejson: {
|
||||||
'bounds': bounds
|
bounds: bounds,
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log(`WARN: MBTiles not in "openmaptiles" format. Serving raw data only...`);
|
console.log(
|
||||||
config['data'][(info.id || 'mbtiles')
|
`WARN: MBTiles not in "openmaptiles" format. Serving raw data only...`,
|
||||||
|
);
|
||||||
|
config['data'][
|
||||||
|
(info.id || 'mbtiles')
|
||||||
.replace(/\//g, '_')
|
.replace(/\//g, '_')
|
||||||
.replace(/:/g, '_')
|
.replace(/:/g, '_')
|
||||||
.replace(/\?/g, '_')] = {
|
.replace(/\?/g, '_')
|
||||||
'mbtiles': path.basename(mbtilesFile)
|
] = {
|
||||||
|
mbtiles: path.basename(mbtilesFile),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -197,7 +188,8 @@ fs.stat(path.resolve(opts.config), (err, stats) => {
|
||||||
console.log(`No MBTiles specified, using ${mbtiles}`);
|
console.log(`No MBTiles specified, using ${mbtiles}`);
|
||||||
return startWithMBTiles(mbtiles);
|
return startWithMBTiles(mbtiles);
|
||||||
} else {
|
} else {
|
||||||
const url = 'https://github.com/maptiler/tileserver-gl/releases/download/v1.3.0/zurich_switzerland.mbtiles';
|
const url =
|
||||||
|
'https://github.com/maptiler/tileserver-gl/releases/download/v1.3.0/zurich_switzerland.mbtiles';
|
||||||
const filename = 'zurich_switzerland.mbtiles';
|
const filename = 'zurich_switzerland.mbtiles';
|
||||||
const stream = fs.createWriteStream(filename);
|
const stream = fs.createWriteStream(filename);
|
||||||
console.log(`No MBTiles found`);
|
console.log(`No MBTiles found`);
|
||||||
|
|
|
@ -16,7 +16,9 @@ export const serve_data = {
|
||||||
init: (options, repo) => {
|
init: (options, repo) => {
|
||||||
const app = express().disable('x-powered-by');
|
const app = express().disable('x-powered-by');
|
||||||
|
|
||||||
app.get('/:id/:z(\\d+)/:x(\\d+)/:y(\\d+).:format([\\w.]+)', (req, res, next) => {
|
app.get(
|
||||||
|
'/:id/:z(\\d+)/:x(\\d+)/:y(\\d+).:format([\\w.]+)',
|
||||||
|
(req, res, next) => {
|
||||||
const item = repo[req.params.id];
|
const item = repo[req.params.id];
|
||||||
if (!item) {
|
if (!item) {
|
||||||
return res.sendStatus(404);
|
return res.sendStatus(404);
|
||||||
|
@ -29,13 +31,21 @@ export const serve_data = {
|
||||||
if (format === options.pbfAlias) {
|
if (format === options.pbfAlias) {
|
||||||
format = 'pbf';
|
format = 'pbf';
|
||||||
}
|
}
|
||||||
if (format !== tileJSONFormat &&
|
if (
|
||||||
!(format === 'geojson' && tileJSONFormat === 'pbf')) {
|
format !== tileJSONFormat &&
|
||||||
|
!(format === 'geojson' && tileJSONFormat === 'pbf')
|
||||||
|
) {
|
||||||
return res.status(404).send('Invalid format');
|
return res.status(404).send('Invalid format');
|
||||||
}
|
}
|
||||||
if (z < item.tileJSON.minzoom || 0 || x < 0 || y < 0 ||
|
if (
|
||||||
|
z < item.tileJSON.minzoom ||
|
||||||
|
0 ||
|
||||||
|
x < 0 ||
|
||||||
|
y < 0 ||
|
||||||
z > item.tileJSON.maxzoom ||
|
z > item.tileJSON.maxzoom ||
|
||||||
x >= Math.pow(2, z) || y >= Math.pow(2, z)) {
|
x >= Math.pow(2, z) ||
|
||||||
|
y >= Math.pow(2, z)
|
||||||
|
) {
|
||||||
return res.status(404).send('Out of bounds');
|
return res.status(404).send('Out of bounds');
|
||||||
}
|
}
|
||||||
item.source.getTile(z, x, y, (err, data, headers) => {
|
item.source.getTile(z, x, y, (err, data, headers) => {
|
||||||
|
@ -51,8 +61,8 @@ export const serve_data = {
|
||||||
return res.status(404).send('Not found');
|
return res.status(404).send('Not found');
|
||||||
} else {
|
} else {
|
||||||
if (tileJSONFormat === 'pbf') {
|
if (tileJSONFormat === 'pbf') {
|
||||||
isGzipped = data.slice(0, 2).indexOf(
|
isGzipped =
|
||||||
Buffer.from([0x1f, 0x8b])) === 0;
|
data.slice(0, 2).indexOf(Buffer.from([0x1f, 0x8b])) === 0;
|
||||||
if (options.dataDecoratorFunc) {
|
if (options.dataDecoratorFunc) {
|
||||||
if (isGzipped) {
|
if (isGzipped) {
|
||||||
data = zlib.unzipSync(data);
|
data = zlib.unzipSync(data);
|
||||||
|
@ -73,8 +83,8 @@ export const serve_data = {
|
||||||
|
|
||||||
const tile = new VectorTile(new Pbf(data));
|
const tile = new VectorTile(new Pbf(data));
|
||||||
const geojson = {
|
const geojson = {
|
||||||
'type': 'FeatureCollection',
|
type: 'FeatureCollection',
|
||||||
'features': []
|
features: [],
|
||||||
};
|
};
|
||||||
for (const layerName in tile.layers) {
|
for (const layerName in tile.layers) {
|
||||||
const layer = tile.layers[layerName];
|
const layer = tile.layers[layerName];
|
||||||
|
@ -100,7 +110,8 @@ export const serve_data = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
app.get('/:id.json', (req, res, next) => {
|
app.get('/:id.json', (req, res, next) => {
|
||||||
const item = repo[req.params.id];
|
const item = repo[req.params.id];
|
||||||
|
@ -108,10 +119,16 @@ export const serve_data = {
|
||||||
return res.sendStatus(404);
|
return res.sendStatus(404);
|
||||||
}
|
}
|
||||||
const info = clone(item.tileJSON);
|
const info = clone(item.tileJSON);
|
||||||
info.tiles = getTileUrls(req, info.tiles,
|
info.tiles = getTileUrls(
|
||||||
`data/${req.params.id}`, info.format, item.publicUrl, {
|
req,
|
||||||
'pbf': options.pbfAlias
|
info.tiles,
|
||||||
});
|
`data/${req.params.id}`,
|
||||||
|
info.format,
|
||||||
|
item.publicUrl,
|
||||||
|
{
|
||||||
|
pbf: options.pbfAlias,
|
||||||
|
},
|
||||||
|
);
|
||||||
return res.send(info);
|
return res.send(info);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -120,7 +137,7 @@ export const serve_data = {
|
||||||
add: (options, repo, params, id, publicUrl) => {
|
add: (options, repo, params, id, publicUrl) => {
|
||||||
const mbtilesFile = path.resolve(options.paths.mbtiles, params.mbtiles);
|
const mbtilesFile = path.resolve(options.paths.mbtiles, params.mbtiles);
|
||||||
let tileJSON = {
|
let tileJSON = {
|
||||||
'tiles': params.domains || options.domains
|
tiles: params.domains || options.domains,
|
||||||
};
|
};
|
||||||
|
|
||||||
const mbtilesFileStats = fs.statSync(mbtilesFile);
|
const mbtilesFileStats = fs.statSync(mbtilesFile);
|
||||||
|
@ -129,7 +146,7 @@ export const serve_data = {
|
||||||
}
|
}
|
||||||
let source;
|
let source;
|
||||||
const sourceInfoPromise = new Promise((resolve, reject) => {
|
const sourceInfoPromise = new Promise((resolve, reject) => {
|
||||||
source = new MBTiles(mbtilesFile + '?mode=ro', err => {
|
source = new MBTiles(mbtilesFile + '?mode=ro', (err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
reject(err);
|
reject(err);
|
||||||
return;
|
return;
|
||||||
|
@ -164,8 +181,8 @@ export const serve_data = {
|
||||||
repo[id] = {
|
repo[id] = {
|
||||||
tileJSON,
|
tileJSON,
|
||||||
publicUrl,
|
publicUrl,
|
||||||
source
|
source,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -26,8 +26,10 @@ export const serve_font = (options, allowedFonts) => {
|
||||||
reject(err);
|
reject(err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (stats.isDirectory() &&
|
if (
|
||||||
fs.existsSync(path.join(fontPath, file, '0-255.pbf'))) {
|
stats.isDirectory() &&
|
||||||
|
fs.existsSync(path.join(fontPath, file, '0-255.pbf'))
|
||||||
|
) {
|
||||||
existingFonts[path.basename(file)] = true;
|
existingFonts[path.basename(file)] = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -40,19 +42,26 @@ export const serve_font = (options, allowedFonts) => {
|
||||||
const fontstack = decodeURI(req.params.fontstack);
|
const fontstack = decodeURI(req.params.fontstack);
|
||||||
const range = req.params.range;
|
const range = req.params.range;
|
||||||
|
|
||||||
getFontsPbf(options.serveAllFonts ? null : allowedFonts,
|
getFontsPbf(
|
||||||
fontPath, fontstack, range, existingFonts).then((concated) => {
|
options.serveAllFonts ? null : allowedFonts,
|
||||||
|
fontPath,
|
||||||
|
fontstack,
|
||||||
|
range,
|
||||||
|
existingFonts,
|
||||||
|
).then(
|
||||||
|
(concated) => {
|
||||||
res.header('Content-type', 'application/x-protobuf');
|
res.header('Content-type', 'application/x-protobuf');
|
||||||
res.header('Last-Modified', lastModified);
|
res.header('Last-Modified', lastModified);
|
||||||
return res.send(concated);
|
return res.send(concated);
|
||||||
}, (err) => res.status(400).send(err)
|
},
|
||||||
|
(err) => res.status(400).send(err),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get('/fonts.json', (req, res, next) => {
|
app.get('/fonts.json', (req, res, next) => {
|
||||||
res.header('Content-type', 'application/json');
|
res.header('Content-type', 'application/json');
|
||||||
return res.send(
|
return res.send(
|
||||||
Object.keys(options.serveAllFonts ? existingFonts : allowedFonts).sort()
|
Object.keys(options.serveAllFonts ? existingFonts : allowedFonts).sort(),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
|
/* eslint-disable @typescript-eslint/no-empty-function */
|
||||||
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
export const serve_rendered = {
|
export const serve_rendered = {
|
||||||
init: (options, repo) => {
|
init: (options, repo) => {},
|
||||||
},
|
add: (options, repo, params, id, publicUrl, dataResolver) => {},
|
||||||
add: (options, repo, params, id, publicUrl, dataResolver) => {
|
remove: (repo, id) => {},
|
||||||
},
|
|
||||||
remove: (repo, id) => {
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -11,7 +11,7 @@ import {createCanvas, Image} from 'canvas';
|
||||||
import clone from 'clone';
|
import clone from 'clone';
|
||||||
import Color from 'color';
|
import Color from 'color';
|
||||||
import express from 'express';
|
import express from 'express';
|
||||||
import sanitize from "sanitize-filename";
|
import sanitize from 'sanitize-filename';
|
||||||
import SphericalMercator from '@mapbox/sphericalmercator';
|
import SphericalMercator from '@mapbox/sphericalmercator';
|
||||||
import mlgl from '@maplibre/maplibre-gl-native';
|
import mlgl from '@maplibre/maplibre-gl-native';
|
||||||
import MBTiles from '@mapbox/mbtiles';
|
import MBTiles from '@mapbox/mbtiles';
|
||||||
|
@ -19,7 +19,7 @@ import proj4 from 'proj4';
|
||||||
import request from 'request';
|
import request from 'request';
|
||||||
import { getFontsPbf, getTileUrls, fixTileJSONCenter } from './utils.js';
|
import { getFontsPbf, getTileUrls, fixTileJSONCenter } from './utils.js';
|
||||||
|
|
||||||
const FLOAT_PATTERN = '[+-]?(?:\\d+|\\d+\.?\\d+)';
|
const FLOAT_PATTERN = '[+-]?(?:\\d+|\\d+.?\\d+)';
|
||||||
const httpTester = /^(http(s)?:)?\/\//;
|
const httpTester = /^(http(s)?:)?\/\//;
|
||||||
|
|
||||||
const mercator = new SphericalMercator();
|
const mercator = new SphericalMercator();
|
||||||
|
@ -38,7 +38,7 @@ const extensionToFormat = {
|
||||||
'.jpg': 'jpeg',
|
'.jpg': 'jpeg',
|
||||||
'.jpeg': 'jpeg',
|
'.jpeg': 'jpeg',
|
||||||
'.png': 'png',
|
'.png': 'png',
|
||||||
'.webp': 'webp'
|
'.webp': 'webp',
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -46,11 +46,12 @@ const extensionToFormat = {
|
||||||
* string is for unknown or unsupported formats.
|
* string is for unknown or unsupported formats.
|
||||||
*/
|
*/
|
||||||
const cachedEmptyResponses = {
|
const cachedEmptyResponses = {
|
||||||
'': Buffer.alloc(0)
|
'': Buffer.alloc(0),
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an appropriate mlgl response for http errors.
|
* Create an appropriate mlgl response for http errors.
|
||||||
|
*
|
||||||
* @param {string} format The format (a sharp format or 'pbf').
|
* @param {string} format The format (a sharp format or 'pbf').
|
||||||
* @param {string} color The background color (or empty string for transparent).
|
* @param {string} color The background color (or empty string for transparent).
|
||||||
* @param {Function} callback The mlgl callback.
|
* @param {Function} callback The mlgl callback.
|
||||||
|
@ -83,9 +84,11 @@ function createEmptyResponse(format, color, callback) {
|
||||||
raw: {
|
raw: {
|
||||||
width: 1,
|
width: 1,
|
||||||
height: 1,
|
height: 1,
|
||||||
channels: channels
|
channels: channels,
|
||||||
}
|
},
|
||||||
}).toFormat(format).toBuffer((err, buffer, info) => {
|
})
|
||||||
|
.toFormat(format)
|
||||||
|
.toBuffer((err, buffer, info) => {
|
||||||
if (!err) {
|
if (!err) {
|
||||||
cachedEmptyResponses[cacheKey] = buffer;
|
cachedEmptyResponses[cacheKey] = buffer;
|
||||||
}
|
}
|
||||||
|
@ -96,8 +99,10 @@ function createEmptyResponse(format, color, callback) {
|
||||||
/**
|
/**
|
||||||
* Parses coordinate pair provided to pair of floats and ensures the resulting
|
* Parses coordinate pair provided to pair of floats and ensures the resulting
|
||||||
* pair is a longitude/latitude combination depending on lnglat query parameter.
|
* pair is a longitude/latitude combination depending on lnglat query parameter.
|
||||||
|
*
|
||||||
* @param {List} coordinatePair Coordinate pair.
|
* @param {List} coordinatePair Coordinate pair.
|
||||||
* @param {Object} query Request query parameters.
|
* @param coordinates
|
||||||
|
* @param {object} query Request query parameters.
|
||||||
*/
|
*/
|
||||||
const parseCoordinatePair = (coordinates, query) => {
|
const parseCoordinatePair = (coordinates, query) => {
|
||||||
const firstCoordinate = parseFloat(coordinates[0]);
|
const firstCoordinate = parseFloat(coordinates[0]);
|
||||||
|
@ -119,8 +124,9 @@ const parseCoordinatePair = (coordinates, query) => {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses a coordinate pair from query arguments and optionally transforms it.
|
* Parses a coordinate pair from query arguments and optionally transforms it.
|
||||||
|
*
|
||||||
* @param {List} coordinatePair Coordinate pair.
|
* @param {List} coordinatePair Coordinate pair.
|
||||||
* @param {Object} query Request query parameters.
|
* @param {object} query Request query parameters.
|
||||||
* @param {Function} transformer Optional transform function.
|
* @param {Function} transformer Optional transform function.
|
||||||
*/
|
*/
|
||||||
const parseCoordinates = (coordinatePair, query, transformer) => {
|
const parseCoordinates = (coordinatePair, query, transformer) => {
|
||||||
|
@ -134,10 +140,10 @@ const parseCoordinates = (coordinatePair, query, transformer) => {
|
||||||
return parsedCoordinates;
|
return parsedCoordinates;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses paths provided via query into a list of path objects.
|
* Parses paths provided via query into a list of path objects.
|
||||||
* @param {Object} query Request query parameters.
|
*
|
||||||
|
* @param {object} query Request query parameters.
|
||||||
* @param {Function} transformer Optional transform function.
|
* @param {Function} transformer Optional transform function.
|
||||||
*/
|
*/
|
||||||
const extractPathsFromQuery = (query, transformer) => {
|
const extractPathsFromQuery = (query, transformer) => {
|
||||||
|
@ -180,9 +186,8 @@ const extractPathsFromQuery = (query, transformer) => {
|
||||||
|
|
||||||
// Extend list of paths with current path if it contains coordinates
|
// Extend list of paths with current path if it contains coordinates
|
||||||
if (currentPath.length) {
|
if (currentPath.length) {
|
||||||
paths.push(currentPath)
|
paths.push(currentPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
return paths;
|
return paths;
|
||||||
};
|
};
|
||||||
|
@ -192,8 +197,9 @@ const extractPathsFromQuery = (query, transformer) => {
|
||||||
* on marker object.
|
* on marker object.
|
||||||
* Options adhere to the following format
|
* Options adhere to the following format
|
||||||
* [optionName]:[optionValue]
|
* [optionName]:[optionValue]
|
||||||
|
*
|
||||||
* @param {List[String]} optionsList List of option strings.
|
* @param {List[String]} optionsList List of option strings.
|
||||||
* @param {Object} marker Marker object to configure.
|
* @param {object} marker Marker object to configure.
|
||||||
*/
|
*/
|
||||||
const parseMarkerOptions = (optionsList, marker) => {
|
const parseMarkerOptions = (optionsList, marker) => {
|
||||||
for (const options of optionsList) {
|
for (const options of optionsList) {
|
||||||
|
@ -207,7 +213,7 @@ const parseMarkerOptions = (optionsList, marker) => {
|
||||||
// Scale factor to up- or downscale icon
|
// Scale factor to up- or downscale icon
|
||||||
case 'scale':
|
case 'scale':
|
||||||
// Scale factors must not be negative
|
// Scale factors must not be negative
|
||||||
marker.scale = Math.abs(parseFloat(optionParts[1]))
|
marker.scale = Math.abs(parseFloat(optionParts[1]));
|
||||||
break;
|
break;
|
||||||
// Icon offset as positive or negative pixel value in the following
|
// Icon offset as positive or negative pixel value in the following
|
||||||
// format [offsetX],[offsetY] where [offsetY] is optional
|
// format [offsetX],[offsetY] where [offsetY] is optional
|
||||||
|
@ -226,8 +232,9 @@ const parseMarkerOptions = (optionsList, marker) => {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses markers provided via query into a list of marker objects.
|
* Parses markers provided via query into a list of marker objects.
|
||||||
* @param {Object} query Request query parameters.
|
*
|
||||||
* @param {Object} options Configuration options.
|
* @param {object} query Request query parameters.
|
||||||
|
* @param {object} options Configuration options.
|
||||||
* @param {Function} transformer Optional transform function.
|
* @param {Function} transformer Optional transform function.
|
||||||
*/
|
*/
|
||||||
const extractMarkersFromQuery = (query, options, transformer) => {
|
const extractMarkersFromQuery = (query, options, transformer) => {
|
||||||
|
@ -240,8 +247,9 @@ const extractMarkersFromQuery = (query, options, transformer) => {
|
||||||
|
|
||||||
// Check if multiple markers have been provided and mimic a list if it's a
|
// Check if multiple markers have been provided and mimic a list if it's a
|
||||||
// single maker.
|
// single maker.
|
||||||
const providedMarkers = Array.isArray(query.marker) ?
|
const providedMarkers = Array.isArray(query.marker)
|
||||||
query.marker : [query.marker];
|
? query.marker
|
||||||
|
: [query.marker];
|
||||||
|
|
||||||
// Iterate through provided markers which can have one of the following
|
// Iterate through provided markers which can have one of the following
|
||||||
// formats
|
// formats
|
||||||
|
@ -266,7 +274,7 @@ const extractMarkersFromQuery = (query, options, transformer) => {
|
||||||
if (!(iconURI.startsWith('http://') || iconURI.startsWith('https://'))) {
|
if (!(iconURI.startsWith('http://') || iconURI.startsWith('https://'))) {
|
||||||
// Sanitize URI with sanitize-filename
|
// Sanitize URI with sanitize-filename
|
||||||
// https://www.npmjs.com/package/sanitize-filename#details
|
// https://www.npmjs.com/package/sanitize-filename#details
|
||||||
iconURI = sanitize(iconURI)
|
iconURI = sanitize(iconURI);
|
||||||
|
|
||||||
// If the selected icon is not part of available icons skip it
|
// If the selected icon is not part of available icons skip it
|
||||||
if (!options.paths.availableIcons.includes(iconURI)) {
|
if (!options.paths.availableIcons.includes(iconURI)) {
|
||||||
|
@ -298,15 +306,15 @@ const extractMarkersFromQuery = (query, options, transformer) => {
|
||||||
|
|
||||||
// Add marker to list
|
// Add marker to list
|
||||||
markers.push(marker);
|
markers.push(marker);
|
||||||
|
|
||||||
}
|
}
|
||||||
return markers;
|
return markers;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transforms coordinates to pixels.
|
* Transforms coordinates to pixels.
|
||||||
|
*
|
||||||
* @param {List[Number]} ll Longitude/Latitude coordinate pair.
|
* @param {List[Number]} ll Longitude/Latitude coordinate pair.
|
||||||
* @param {Number} zoom Map zoom level.
|
* @param {number} zoom Map zoom level.
|
||||||
*/
|
*/
|
||||||
const precisePx = (ll, zoom) => {
|
const precisePx = (ll, zoom) => {
|
||||||
const px = mercator.px(ll, 20);
|
const px = mercator.px(ll, 20);
|
||||||
|
@ -316,12 +324,13 @@ const precisePx = (ll, zoom) => {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Draws a marker in cavans context.
|
* Draws a marker in cavans context.
|
||||||
* @param {Object} ctx Canvas context object.
|
*
|
||||||
* @param {Object} marker Marker object parsed by extractMarkersFromQuery.
|
* @param {object} ctx Canvas context object.
|
||||||
* @param {Number} z Map zoom level.
|
* @param {object} marker Marker object parsed by extractMarkersFromQuery.
|
||||||
|
* @param {number} z Map zoom level.
|
||||||
*/
|
*/
|
||||||
const drawMarker = (ctx, marker, z) => {
|
const drawMarker = (ctx, marker, z) => {
|
||||||
return new Promise(resolve => {
|
return new Promise((resolve) => {
|
||||||
const img = new Image();
|
const img = new Image();
|
||||||
const pixelCoords = precisePx(marker.location, z);
|
const pixelCoords = precisePx(marker.location, z);
|
||||||
|
|
||||||
|
@ -340,15 +349,15 @@ const drawMarker = (ctx, marker, z) => {
|
||||||
// scaled as well. Additionally offsets are provided as either positive or
|
// scaled as well. Additionally offsets are provided as either positive or
|
||||||
// negative values so we always add them
|
// negative values so we always add them
|
||||||
if (marker.offsetX) {
|
if (marker.offsetX) {
|
||||||
xCoordinate = xCoordinate + (marker.offsetX * scale);
|
xCoordinate = xCoordinate + marker.offsetX * scale;
|
||||||
}
|
}
|
||||||
if (marker.offsetY) {
|
if (marker.offsetY) {
|
||||||
yCoordinate = yCoordinate + (marker.offsetY * scale);
|
yCoordinate = yCoordinate + marker.offsetY * scale;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'x': xCoordinate,
|
x: xCoordinate,
|
||||||
'y': yCoordinate
|
y: yCoordinate,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -375,19 +384,22 @@ const drawMarker = (ctx, marker, z) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
img.onload = drawOnCanvas;
|
img.onload = drawOnCanvas;
|
||||||
img.onerror = err => { throw err };
|
img.onerror = (err) => {
|
||||||
|
throw err;
|
||||||
|
};
|
||||||
img.src = marker.icon;
|
img.src = marker.icon;
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Draws a list of markers onto a canvas.
|
* Draws a list of markers onto a canvas.
|
||||||
* Wraps drawing of markers into list of promises and awaits them.
|
* Wraps drawing of markers into list of promises and awaits them.
|
||||||
* It's required because images are expected to load asynchronous in canvas js
|
* It's required because images are expected to load asynchronous in canvas js
|
||||||
* even when provided from a local disk.
|
* even when provided from a local disk.
|
||||||
* @param {Object} ctx Canvas context object.
|
*
|
||||||
|
* @param {object} ctx Canvas context object.
|
||||||
* @param {List[Object]} markers Marker objects parsed by extractMarkersFromQuery.
|
* @param {List[Object]} markers Marker objects parsed by extractMarkersFromQuery.
|
||||||
* @param {Number} z Map zoom level.
|
* @param {number} z Map zoom level.
|
||||||
*/
|
*/
|
||||||
const drawMarkers = async (ctx, markers, z) => {
|
const drawMarkers = async (ctx, markers, z) => {
|
||||||
const markerPromises = [];
|
const markerPromises = [];
|
||||||
|
@ -399,14 +411,15 @@ const drawMarkers = async (ctx, markers, z) => {
|
||||||
|
|
||||||
// Await marker drawings before continuing
|
// Await marker drawings before continuing
|
||||||
await Promise.all(markerPromises);
|
await Promise.all(markerPromises);
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Draws a list of coordinates onto a canvas and styles the resulting path.
|
* Draws a list of coordinates onto a canvas and styles the resulting path.
|
||||||
* @param {Object} ctx Canvas context object.
|
*
|
||||||
|
* @param {object} ctx Canvas context object.
|
||||||
* @param {List[Number]} path List of coordinates.
|
* @param {List[Number]} path List of coordinates.
|
||||||
* @param {Object} query Request query parameters.
|
* @param {object} query Request query parameters.
|
||||||
* @param {Number} z Map zoom level.
|
* @param {number} z Map zoom level.
|
||||||
*/
|
*/
|
||||||
const drawPath = (ctx, path, query, z) => {
|
const drawPath = (ctx, path, query, z) => {
|
||||||
if (!path || path.length < 2) {
|
if (!path || path.length < 2) {
|
||||||
|
@ -422,8 +435,10 @@ const drawPath = (ctx, path, query, z) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if first coordinate matches last coordinate
|
// Check if first coordinate matches last coordinate
|
||||||
if (path[0][0] === path[path.length - 1][0] &&
|
if (
|
||||||
path[0][1] === path[path.length - 1][1]) {
|
path[0][0] === path[path.length - 1][0] &&
|
||||||
|
path[0][1] === path[path.length - 1][1]
|
||||||
|
) {
|
||||||
ctx.closePath();
|
ctx.closePath();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -434,14 +449,15 @@ const drawPath = (ctx, path, query, z) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get line width from query and fall back to 1 if not provided
|
// Get line width from query and fall back to 1 if not provided
|
||||||
const lineWidth = query.width !== undefined ?
|
const lineWidth = query.width !== undefined ? parseFloat(query.width) : 1;
|
||||||
parseFloat(query.width) : 1;
|
|
||||||
|
|
||||||
// Ensure line width is valid
|
// Ensure line width is valid
|
||||||
if (lineWidth > 0) {
|
if (lineWidth > 0) {
|
||||||
// Get border width from query and fall back to 10% of line width
|
// Get border width from query and fall back to 10% of line width
|
||||||
const borderWidth = query.borderwidth !== undefined ?
|
const borderWidth =
|
||||||
parseFloat(query.borderwidth) : lineWidth * 0.1;
|
query.borderwidth !== undefined
|
||||||
|
? parseFloat(query.borderwidth)
|
||||||
|
: lineWidth * 0.1;
|
||||||
|
|
||||||
// Set rendering style for the start and end points of the path
|
// Set rendering style for the start and end points of the path
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineCap
|
// https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineCap
|
||||||
|
@ -456,7 +472,7 @@ const drawPath = (ctx, path, query, z) => {
|
||||||
if (query.border !== undefined && borderWidth > 0) {
|
if (query.border !== undefined && borderWidth > 0) {
|
||||||
// We need to double the desired border width and add it to the line width
|
// We need to double the desired border width and add it to the line width
|
||||||
// in order to get the desired border on each side of the line.
|
// in order to get the desired border on each side of the line.
|
||||||
ctx.lineWidth = lineWidth + (borderWidth * 2);
|
ctx.lineWidth = lineWidth + borderWidth * 2;
|
||||||
// Set border style as rgba
|
// Set border style as rgba
|
||||||
ctx.strokeStyle = query.border;
|
ctx.strokeStyle = query.border;
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
|
@ -466,9 +482,21 @@ const drawPath = (ctx, path, query, z) => {
|
||||||
ctx.strokeStyle = query.stroke || 'rgba(0,64,255,0.7)';
|
ctx.strokeStyle = query.stroke || 'rgba(0,64,255,0.7)';
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const renderOverlay = async (z, x, y, bearing, pitch, w, h, scale, paths, markers, query) => {
|
const renderOverlay = async (
|
||||||
|
z,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
bearing,
|
||||||
|
pitch,
|
||||||
|
w,
|
||||||
|
h,
|
||||||
|
scale,
|
||||||
|
paths,
|
||||||
|
markers,
|
||||||
|
query,
|
||||||
|
) => {
|
||||||
if ((!paths || paths.length === 0) && (!markers || markers.length === 0)) {
|
if ((!paths || paths.length === 0) && (!markers || markers.length === 0)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -479,7 +507,7 @@ const renderOverlay = async (z, x, y, bearing, pitch, w, h, scale, paths, marker
|
||||||
const maxEdge = center[1] + h / 2;
|
const maxEdge = center[1] + h / 2;
|
||||||
const minEdge = center[1] - h / 2;
|
const minEdge = center[1] - h / 2;
|
||||||
if (maxEdge > mapHeight) {
|
if (maxEdge > mapHeight) {
|
||||||
center[1] -= (maxEdge - mapHeight);
|
center[1] -= maxEdge - mapHeight;
|
||||||
} else if (minEdge < 0) {
|
} else if (minEdge < 0) {
|
||||||
center[1] -= minEdge;
|
center[1] -= minEdge;
|
||||||
}
|
}
|
||||||
|
@ -489,7 +517,7 @@ const renderOverlay = async (z, x, y, bearing, pitch, w, h, scale, paths, marker
|
||||||
ctx.scale(scale, scale);
|
ctx.scale(scale, scale);
|
||||||
if (bearing) {
|
if (bearing) {
|
||||||
ctx.translate(w / 2, h / 2);
|
ctx.translate(w / 2, h / 2);
|
||||||
ctx.rotate(-bearing / 180 * Math.PI);
|
ctx.rotate((-bearing / 180) * Math.PI);
|
||||||
ctx.translate(-center[0], -center[1]);
|
ctx.translate(-center[0], -center[1]);
|
||||||
} else {
|
} else {
|
||||||
// optimized path
|
// optimized path
|
||||||
|
@ -510,17 +538,17 @@ const renderOverlay = async (z, x, y, bearing, pitch, w, h, scale, paths, marker
|
||||||
const calcZForBBox = (bbox, w, h, query) => {
|
const calcZForBBox = (bbox, w, h, query) => {
|
||||||
let z = 25;
|
let z = 25;
|
||||||
|
|
||||||
const padding = query.padding !== undefined ?
|
const padding = query.padding !== undefined ? parseFloat(query.padding) : 0.1;
|
||||||
parseFloat(query.padding) : 0.1;
|
|
||||||
|
|
||||||
const minCorner = mercator.px([bbox[0], bbox[3]], z);
|
const minCorner = mercator.px([bbox[0], bbox[3]], z);
|
||||||
const maxCorner = mercator.px([bbox[2], bbox[1]], z);
|
const maxCorner = mercator.px([bbox[2], bbox[1]], z);
|
||||||
const w_ = w / (1 + 2 * padding);
|
const w_ = w / (1 + 2 * padding);
|
||||||
const h_ = h / (1 + 2 * padding);
|
const h_ = h / (1 + 2 * padding);
|
||||||
|
|
||||||
z -= Math.max(
|
z -=
|
||||||
|
Math.max(
|
||||||
Math.log((maxCorner[0] - minCorner[0]) / w_),
|
Math.log((maxCorner[0] - minCorner[0]) / w_),
|
||||||
Math.log((maxCorner[1] - minCorner[1]) / h_)
|
Math.log((maxCorner[1] - minCorner[1]) / h_),
|
||||||
) / Math.LN2;
|
) / Math.LN2;
|
||||||
|
|
||||||
z = Math.max(Math.log(Math.max(w, h) / 256) / Math.LN2, Math.min(25, z));
|
z = Math.max(Math.log(Math.max(w, h) / 256) / Math.LN2, Math.min(25, z));
|
||||||
|
@ -563,14 +591,36 @@ export const serve_rendered = {
|
||||||
|
|
||||||
const app = express().disable('x-powered-by');
|
const app = express().disable('x-powered-by');
|
||||||
|
|
||||||
const respondImage = (item, z, lon, lat, bearing, pitch, width, height, scale, format, res, next, opt_overlay, opt_mode='tile') => {
|
const respondImage = (
|
||||||
if (Math.abs(lon) > 180 || Math.abs(lat) > 85.06 ||
|
item,
|
||||||
lon !== lon || lat !== lat) {
|
z,
|
||||||
|
lon,
|
||||||
|
lat,
|
||||||
|
bearing,
|
||||||
|
pitch,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
scale,
|
||||||
|
format,
|
||||||
|
res,
|
||||||
|
next,
|
||||||
|
opt_overlay,
|
||||||
|
opt_mode = 'tile',
|
||||||
|
) => {
|
||||||
|
if (
|
||||||
|
Math.abs(lon) > 180 ||
|
||||||
|
Math.abs(lat) > 85.06 ||
|
||||||
|
lon !== lon ||
|
||||||
|
lat !== lat
|
||||||
|
) {
|
||||||
return res.status(400).send('Invalid center');
|
return res.status(400).send('Invalid center');
|
||||||
}
|
}
|
||||||
if (Math.min(width, height) <= 0 ||
|
if (
|
||||||
|
Math.min(width, height) <= 0 ||
|
||||||
Math.max(width, height) * scale > (options.maxSize || 2048) ||
|
Math.max(width, height) * scale > (options.maxSize || 2048) ||
|
||||||
width !== width || height !== height) {
|
width !== width ||
|
||||||
|
height !== height
|
||||||
|
) {
|
||||||
return res.status(400).send('Invalid size');
|
return res.status(400).send('Invalid size');
|
||||||
}
|
}
|
||||||
if (format === 'png' || format === 'webp') {
|
if (format === 'png' || format === 'webp') {
|
||||||
|
@ -594,7 +644,7 @@ export const serve_rendered = {
|
||||||
bearing: bearing,
|
bearing: bearing,
|
||||||
pitch: pitch,
|
pitch: pitch,
|
||||||
width: width,
|
width: width,
|
||||||
height: height
|
height: height,
|
||||||
};
|
};
|
||||||
if (z === 0) {
|
if (z === 0) {
|
||||||
params.width *= 2;
|
params.width *= 2;
|
||||||
|
@ -634,18 +684,21 @@ export const serve_rendered = {
|
||||||
raw: {
|
raw: {
|
||||||
width: params.width * scale,
|
width: params.width * scale,
|
||||||
height: params.height * scale,
|
height: params.height * scale,
|
||||||
channels: 4
|
channels: 4,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (z > 2 && tileMargin > 0) {
|
if (z > 2 && tileMargin > 0) {
|
||||||
const [_, y] = mercator.px(params.center, z);
|
const [_, y] = mercator.px(params.center, z);
|
||||||
let yoffset = Math.max(Math.min(0, y - 128 - tileMargin), y + 128 + tileMargin - Math.pow(2, z + 8));
|
let yoffset = Math.max(
|
||||||
|
Math.min(0, y - 128 - tileMargin),
|
||||||
|
y + 128 + tileMargin - Math.pow(2, z + 8),
|
||||||
|
);
|
||||||
image.extract({
|
image.extract({
|
||||||
left: tileMargin * scale,
|
left: tileMargin * scale,
|
||||||
top: (tileMargin + yoffset) * scale,
|
top: (tileMargin + yoffset) * scale,
|
||||||
width: width * scale,
|
width: width * scale,
|
||||||
height: height * scale
|
height: height * scale,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -687,7 +740,7 @@ export const serve_rendered = {
|
||||||
|
|
||||||
res.set({
|
res.set({
|
||||||
'Last-Modified': item.lastModified,
|
'Last-Modified': item.lastModified,
|
||||||
'Content-Type': `image/${format}`
|
'Content-Type': `image/${format}`,
|
||||||
});
|
});
|
||||||
return res.status(200).send(buffer);
|
return res.status(200).send(buffer);
|
||||||
});
|
});
|
||||||
|
@ -695,13 +748,16 @@ export const serve_rendered = {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
app.get(`/:id/:z(\\d+)/:x(\\d+)/:y(\\d+):scale(${scalePattern})?.:format([\\w]+)`, (req, res, next) => {
|
app.get(
|
||||||
|
`/:id/:z(\\d+)/:x(\\d+)/:y(\\d+):scale(${scalePattern})?.:format([\\w]+)`,
|
||||||
|
(req, res, next) => {
|
||||||
const item = repo[req.params.id];
|
const item = repo[req.params.id];
|
||||||
if (!item) {
|
if (!item) {
|
||||||
return res.sendStatus(404);
|
return res.sendStatus(404);
|
||||||
}
|
}
|
||||||
|
|
||||||
const modifiedSince = req.get('if-modified-since'); const cc = req.get('cache-control');
|
const modifiedSince = req.get('if-modified-since');
|
||||||
|
const cc = req.get('cache-control');
|
||||||
if (modifiedSince && (!cc || cc.indexOf('no-cache') === -1)) {
|
if (modifiedSince && (!cc || cc.indexOf('no-cache') === -1)) {
|
||||||
if (new Date(item.lastModified) <= new Date(modifiedSince)) {
|
if (new Date(item.lastModified) <= new Date(modifiedSince)) {
|
||||||
return res.sendStatus(304);
|
return res.sendStatus(304);
|
||||||
|
@ -713,28 +769,56 @@ export const serve_rendered = {
|
||||||
const y = req.params.y | 0;
|
const y = req.params.y | 0;
|
||||||
const scale = getScale(req.params.scale);
|
const scale = getScale(req.params.scale);
|
||||||
const format = req.params.format;
|
const format = req.params.format;
|
||||||
if (z < 0 || x < 0 || y < 0 ||
|
if (
|
||||||
z > 22 || x >= Math.pow(2, z) || y >= Math.pow(2, z)) {
|
z < 0 ||
|
||||||
|
x < 0 ||
|
||||||
|
y < 0 ||
|
||||||
|
z > 22 ||
|
||||||
|
x >= Math.pow(2, z) ||
|
||||||
|
y >= Math.pow(2, z)
|
||||||
|
) {
|
||||||
return res.status(404).send('Out of bounds');
|
return res.status(404).send('Out of bounds');
|
||||||
}
|
}
|
||||||
const tileSize = 256;
|
const tileSize = 256;
|
||||||
const tileCenter = mercator.ll([
|
const tileCenter = mercator.ll(
|
||||||
|
[
|
||||||
((x + 0.5) / (1 << z)) * (256 << z),
|
((x + 0.5) / (1 << z)) * (256 << z),
|
||||||
((y + 0.5) / (1 << z)) * (256 << z)
|
((y + 0.5) / (1 << z)) * (256 << z),
|
||||||
], z);
|
],
|
||||||
return respondImage(item, z, tileCenter[0], tileCenter[1], 0, 0, tileSize, tileSize, scale, format, res, next);
|
z,
|
||||||
});
|
);
|
||||||
|
return respondImage(
|
||||||
|
item,
|
||||||
|
z,
|
||||||
|
tileCenter[0],
|
||||||
|
tileCenter[1],
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
tileSize,
|
||||||
|
tileSize,
|
||||||
|
scale,
|
||||||
|
format,
|
||||||
|
res,
|
||||||
|
next,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
if (options.serveStaticMaps !== false) {
|
if (options.serveStaticMaps !== false) {
|
||||||
const staticPattern =
|
const staticPattern = `/:id/static/:raw(raw)?/%s/:width(\\d+)x:height(\\d+):scale(${scalePattern})?.:format([\\w]+)`;
|
||||||
`/:id/static/:raw(raw)?/%s/:width(\\d+)x:height(\\d+):scale(${scalePattern})?.:format([\\w]+)`;
|
|
||||||
|
|
||||||
const centerPattern =
|
const centerPattern = util.format(
|
||||||
util.format(':x(%s),:y(%s),:z(%s)(@:bearing(%s)(,:pitch(%s))?)?',
|
':x(%s),:y(%s),:z(%s)(@:bearing(%s)(,:pitch(%s))?)?',
|
||||||
FLOAT_PATTERN, FLOAT_PATTERN, FLOAT_PATTERN,
|
FLOAT_PATTERN,
|
||||||
FLOAT_PATTERN, FLOAT_PATTERN);
|
FLOAT_PATTERN,
|
||||||
|
FLOAT_PATTERN,
|
||||||
|
FLOAT_PATTERN,
|
||||||
|
FLOAT_PATTERN,
|
||||||
|
);
|
||||||
|
|
||||||
app.get(util.format(staticPattern, centerPattern), async (req, res, next) => {
|
app.get(
|
||||||
|
util.format(staticPattern, centerPattern),
|
||||||
|
async (req, res, next) => {
|
||||||
const item = repo[req.params.id];
|
const item = repo[req.params.id];
|
||||||
if (!item) {
|
if (!item) {
|
||||||
return res.sendStatus(404);
|
return res.sendStatus(404);
|
||||||
|
@ -754,8 +838,9 @@ export const serve_rendered = {
|
||||||
return res.status(404).send('Invalid zoom');
|
return res.status(404).send('Invalid zoom');
|
||||||
}
|
}
|
||||||
|
|
||||||
const transformer = raw ?
|
const transformer = raw
|
||||||
mercator.inverse.bind(mercator) : item.dataProjWGStoInternalWGS;
|
? mercator.inverse.bind(mercator)
|
||||||
|
: item.dataProjWGStoInternalWGS;
|
||||||
|
|
||||||
if (transformer) {
|
if (transformer) {
|
||||||
const ll = transformer([x, y]);
|
const ll = transformer([x, y]);
|
||||||
|
@ -764,11 +849,43 @@ export const serve_rendered = {
|
||||||
}
|
}
|
||||||
|
|
||||||
const paths = extractPathsFromQuery(req.query, transformer);
|
const paths = extractPathsFromQuery(req.query, transformer);
|
||||||
const markers = extractMarkersFromQuery(req.query, options, transformer);
|
const markers = extractMarkersFromQuery(
|
||||||
const overlay = await renderOverlay(z, x, y, bearing, pitch, w, h, scale, paths, markers, req.query);
|
req.query,
|
||||||
|
options,
|
||||||
|
transformer,
|
||||||
|
);
|
||||||
|
const overlay = await renderOverlay(
|
||||||
|
z,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
bearing,
|
||||||
|
pitch,
|
||||||
|
w,
|
||||||
|
h,
|
||||||
|
scale,
|
||||||
|
paths,
|
||||||
|
markers,
|
||||||
|
req.query,
|
||||||
|
);
|
||||||
|
|
||||||
return respondImage(item, z, x, y, bearing, pitch, w, h, scale, format, res, next, overlay, 'static');
|
return respondImage(
|
||||||
});
|
item,
|
||||||
|
z,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
bearing,
|
||||||
|
pitch,
|
||||||
|
w,
|
||||||
|
h,
|
||||||
|
scale,
|
||||||
|
format,
|
||||||
|
res,
|
||||||
|
next,
|
||||||
|
overlay,
|
||||||
|
'static',
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
const serveBounds = async (req, res, next) => {
|
const serveBounds = async (req, res, next) => {
|
||||||
const item = repo[req.params.id];
|
const item = repo[req.params.id];
|
||||||
|
@ -776,11 +893,17 @@ export const serve_rendered = {
|
||||||
return res.sendStatus(404);
|
return res.sendStatus(404);
|
||||||
}
|
}
|
||||||
const raw = req.params.raw;
|
const raw = req.params.raw;
|
||||||
const bbox = [+req.params.minx, +req.params.miny, +req.params.maxx, +req.params.maxy];
|
const bbox = [
|
||||||
|
+req.params.minx,
|
||||||
|
+req.params.miny,
|
||||||
|
+req.params.maxx,
|
||||||
|
+req.params.maxy,
|
||||||
|
];
|
||||||
let center = [(bbox[0] + bbox[2]) / 2, (bbox[1] + bbox[3]) / 2];
|
let center = [(bbox[0] + bbox[2]) / 2, (bbox[1] + bbox[3]) / 2];
|
||||||
|
|
||||||
const transformer = raw ?
|
const transformer = raw
|
||||||
mercator.inverse.bind(mercator) : item.dataProjWGStoInternalWGS;
|
? mercator.inverse.bind(mercator)
|
||||||
|
: item.dataProjWGStoInternalWGS;
|
||||||
|
|
||||||
if (transformer) {
|
if (transformer) {
|
||||||
const minCorner = transformer(bbox.slice(0, 2));
|
const minCorner = transformer(bbox.slice(0, 2));
|
||||||
|
@ -804,14 +927,49 @@ export const serve_rendered = {
|
||||||
const pitch = 0;
|
const pitch = 0;
|
||||||
|
|
||||||
const paths = extractPathsFromQuery(req.query, transformer);
|
const paths = extractPathsFromQuery(req.query, transformer);
|
||||||
const markers = extractMarkersFromQuery(req.query, options, transformer);
|
const markers = extractMarkersFromQuery(
|
||||||
const overlay = await renderOverlay(z, x, y, bearing, pitch, w, h, scale, paths, markers, req.query);
|
req.query,
|
||||||
return respondImage(item, z, x, y, bearing, pitch, w, h, scale, format, res, next, overlay, 'static');
|
options,
|
||||||
|
transformer,
|
||||||
|
);
|
||||||
|
const overlay = await renderOverlay(
|
||||||
|
z,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
bearing,
|
||||||
|
pitch,
|
||||||
|
w,
|
||||||
|
h,
|
||||||
|
scale,
|
||||||
|
paths,
|
||||||
|
markers,
|
||||||
|
req.query,
|
||||||
|
);
|
||||||
|
return respondImage(
|
||||||
|
item,
|
||||||
|
z,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
bearing,
|
||||||
|
pitch,
|
||||||
|
w,
|
||||||
|
h,
|
||||||
|
scale,
|
||||||
|
format,
|
||||||
|
res,
|
||||||
|
next,
|
||||||
|
overlay,
|
||||||
|
'static',
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const boundsPattern =
|
const boundsPattern = util.format(
|
||||||
util.format(':minx(%s),:miny(%s),:maxx(%s),:maxy(%s)',
|
':minx(%s),:miny(%s),:maxx(%s),:maxy(%s)',
|
||||||
FLOAT_PATTERN, FLOAT_PATTERN, FLOAT_PATTERN, FLOAT_PATTERN);
|
FLOAT_PATTERN,
|
||||||
|
FLOAT_PATTERN,
|
||||||
|
FLOAT_PATTERN,
|
||||||
|
FLOAT_PATTERN,
|
||||||
|
);
|
||||||
|
|
||||||
app.get(util.format(staticPattern, boundsPattern), serveBounds);
|
app.get(util.format(staticPattern, boundsPattern), serveBounds);
|
||||||
|
|
||||||
|
@ -839,7 +997,9 @@ export const serve_rendered = {
|
||||||
|
|
||||||
const autoPattern = 'auto';
|
const autoPattern = 'auto';
|
||||||
|
|
||||||
app.get(util.format(staticPattern, autoPattern), async (req, res, next) => {
|
app.get(
|
||||||
|
util.format(staticPattern, autoPattern),
|
||||||
|
async (req, res, next) => {
|
||||||
const item = repo[req.params.id];
|
const item = repo[req.params.id];
|
||||||
if (!item) {
|
if (!item) {
|
||||||
return res.sendStatus(404);
|
return res.sendStatus(404);
|
||||||
|
@ -852,11 +1012,16 @@ export const serve_rendered = {
|
||||||
const scale = getScale(req.params.scale);
|
const scale = getScale(req.params.scale);
|
||||||
const format = req.params.format;
|
const format = req.params.format;
|
||||||
|
|
||||||
const transformer = raw ?
|
const transformer = raw
|
||||||
mercator.inverse.bind(mercator) : item.dataProjWGStoInternalWGS;
|
? mercator.inverse.bind(mercator)
|
||||||
|
: item.dataProjWGStoInternalWGS;
|
||||||
|
|
||||||
const paths = extractPathsFromQuery(req.query, transformer);
|
const paths = extractPathsFromQuery(req.query, transformer);
|
||||||
const markers = extractMarkersFromQuery(req.query, options, transformer);
|
const markers = extractMarkersFromQuery(
|
||||||
|
req.query,
|
||||||
|
options,
|
||||||
|
transformer,
|
||||||
|
);
|
||||||
|
|
||||||
// Extract coordinates from markers
|
// Extract coordinates from markers
|
||||||
const markerCoordinates = [];
|
const markerCoordinates = [];
|
||||||
|
@ -865,7 +1030,7 @@ export const serve_rendered = {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create array with coordinates from markers and path
|
// Create array with coordinates from markers and path
|
||||||
const coords = new Array().concat(paths.flat()).concat(markerCoordinates);
|
const coords = [].concat(paths.flat()).concat(markerCoordinates);
|
||||||
|
|
||||||
// Check if we have at least one coordinate to calculate a bounding box
|
// Check if we have at least one coordinate to calculate a bounding box
|
||||||
if (coords.length < 1) {
|
if (coords.length < 1) {
|
||||||
|
@ -881,9 +1046,10 @@ export const serve_rendered = {
|
||||||
}
|
}
|
||||||
|
|
||||||
const bbox_ = mercator.convert(bbox, '900913');
|
const bbox_ = mercator.convert(bbox, '900913');
|
||||||
const center = mercator.inverse(
|
const center = mercator.inverse([
|
||||||
[(bbox_[0] + bbox_[2]) / 2, (bbox_[1] + bbox_[3]) / 2]
|
(bbox_[0] + bbox_[2]) / 2,
|
||||||
);
|
(bbox_[1] + bbox_[3]) / 2,
|
||||||
|
]);
|
||||||
|
|
||||||
// Calculate zoom level
|
// Calculate zoom level
|
||||||
const maxZoom = parseFloat(req.query.maxzoom);
|
const maxZoom = parseFloat(req.query.maxzoom);
|
||||||
|
@ -895,10 +1061,38 @@ export const serve_rendered = {
|
||||||
const x = center[0];
|
const x = center[0];
|
||||||
const y = center[1];
|
const y = center[1];
|
||||||
|
|
||||||
const overlay = await renderOverlay(z, x, y, bearing, pitch, w, h, scale, paths, markers, req.query);
|
const overlay = await renderOverlay(
|
||||||
|
z,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
bearing,
|
||||||
|
pitch,
|
||||||
|
w,
|
||||||
|
h,
|
||||||
|
scale,
|
||||||
|
paths,
|
||||||
|
markers,
|
||||||
|
req.query,
|
||||||
|
);
|
||||||
|
|
||||||
return respondImage(item, z, x, y, bearing, pitch, w, h, scale, format, res, next, overlay, 'static');
|
return respondImage(
|
||||||
});
|
item,
|
||||||
|
z,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
bearing,
|
||||||
|
pitch,
|
||||||
|
w,
|
||||||
|
h,
|
||||||
|
scale,
|
||||||
|
format,
|
||||||
|
res,
|
||||||
|
next,
|
||||||
|
overlay,
|
||||||
|
'static',
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
app.get('/:id.json', (req, res, next) => {
|
app.get('/:id.json', (req, res, next) => {
|
||||||
|
@ -907,8 +1101,13 @@ export const serve_rendered = {
|
||||||
return res.sendStatus(404);
|
return res.sendStatus(404);
|
||||||
}
|
}
|
||||||
const info = clone(item.tileJSON);
|
const info = clone(item.tileJSON);
|
||||||
info.tiles = getTileUrls(req, info.tiles,
|
info.tiles = getTileUrls(
|
||||||
`styles/${req.params.id}`, info.format, item.publicUrl);
|
req,
|
||||||
|
info.tiles,
|
||||||
|
`styles/${req.params.id}`,
|
||||||
|
info.format,
|
||||||
|
item.publicUrl,
|
||||||
|
);
|
||||||
return res.send(info);
|
return res.send(info);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -918,7 +1117,7 @@ export const serve_rendered = {
|
||||||
const map = {
|
const map = {
|
||||||
renderers: [],
|
renderers: [],
|
||||||
renderers_static: [],
|
renderers_static: [],
|
||||||
sources: {}
|
sources: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
let styleJSON;
|
let styleJSON;
|
||||||
|
@ -941,12 +1140,19 @@ export const serve_rendered = {
|
||||||
const fontstack = unescape(parts[2]);
|
const fontstack = unescape(parts[2]);
|
||||||
const range = parts[3].split('.')[0];
|
const range = parts[3].split('.')[0];
|
||||||
getFontsPbf(
|
getFontsPbf(
|
||||||
null, options.paths[protocol], fontstack, range, existingFonts
|
null,
|
||||||
).then((concated) => {
|
options.paths[protocol],
|
||||||
|
fontstack,
|
||||||
|
range,
|
||||||
|
existingFonts,
|
||||||
|
).then(
|
||||||
|
(concated) => {
|
||||||
callback(null, { data: concated });
|
callback(null, { data: concated });
|
||||||
}, (err) => {
|
},
|
||||||
|
(err) => {
|
||||||
callback(err, { data: null });
|
callback(err, { data: null });
|
||||||
});
|
},
|
||||||
|
);
|
||||||
} else if (protocol === 'mbtiles') {
|
} else if (protocol === 'mbtiles') {
|
||||||
const parts = req.url.split('/');
|
const parts = req.url.split('/');
|
||||||
const sourceId = parts[2];
|
const sourceId = parts[2];
|
||||||
|
@ -958,8 +1164,13 @@ export const serve_rendered = {
|
||||||
const format = parts[5].split('.')[1];
|
const format = parts[5].split('.')[1];
|
||||||
source.getTile(z, x, y, (err, data, headers) => {
|
source.getTile(z, x, y, (err, data, headers) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
if (options.verbose) console.log('MBTiles error, serving empty', err);
|
if (options.verbose)
|
||||||
createEmptyResponse(sourceInfo.format, sourceInfo.color, callback);
|
console.log('MBTiles error, serving empty', err);
|
||||||
|
createEmptyResponse(
|
||||||
|
sourceInfo.format,
|
||||||
|
sourceInfo.color,
|
||||||
|
callback,
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -972,11 +1183,23 @@ export const serve_rendered = {
|
||||||
try {
|
try {
|
||||||
response.data = zlib.unzipSync(data);
|
response.data = zlib.unzipSync(data);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log('Skipping incorrect header for tile mbtiles://%s/%s/%s/%s.pbf', id, z, x, y);
|
console.log(
|
||||||
|
'Skipping incorrect header for tile mbtiles://%s/%s/%s/%s.pbf',
|
||||||
|
id,
|
||||||
|
z,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (options.dataDecoratorFunc) {
|
if (options.dataDecoratorFunc) {
|
||||||
response.data = options.dataDecoratorFunc(
|
response.data = options.dataDecoratorFunc(
|
||||||
sourceId, 'data', response.data, z, x, y);
|
sourceId,
|
||||||
|
'data',
|
||||||
|
response.data,
|
||||||
|
z,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
response.data = data;
|
response.data = data;
|
||||||
|
@ -985,11 +1208,13 @@ export const serve_rendered = {
|
||||||
callback(null, response);
|
callback(null, response);
|
||||||
});
|
});
|
||||||
} else if (protocol === 'http' || protocol === 'https') {
|
} else if (protocol === 'http' || protocol === 'https') {
|
||||||
request({
|
request(
|
||||||
|
{
|
||||||
url: req.url,
|
url: req.url,
|
||||||
encoding: null,
|
encoding: null,
|
||||||
gzip: true
|
gzip: true,
|
||||||
}, (err, res, body) => {
|
},
|
||||||
|
(err, res, body) => {
|
||||||
const parts = url.parse(req.url);
|
const parts = url.parse(req.url);
|
||||||
const extension = path.extname(parts.pathname).toLowerCase();
|
const extension = path.extname(parts.pathname).toLowerCase();
|
||||||
const format = extensionToFormat[extension] || '';
|
const format = extensionToFormat[extension] || '';
|
||||||
|
@ -1012,9 +1237,10 @@ export const serve_rendered = {
|
||||||
|
|
||||||
response.data = body;
|
response.data = body;
|
||||||
callback(null, response);
|
callback(null, response);
|
||||||
});
|
},
|
||||||
}
|
);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
});
|
});
|
||||||
renderer.load(styleJSON);
|
renderer.load(styleJSON);
|
||||||
createCallback(null, renderer);
|
createCallback(null, renderer);
|
||||||
|
@ -1025,7 +1251,7 @@ export const serve_rendered = {
|
||||||
create: createRenderer.bind(null, ratio),
|
create: createRenderer.bind(null, ratio),
|
||||||
destroy: (renderer) => {
|
destroy: (renderer) => {
|
||||||
renderer.release();
|
renderer.release();
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1039,16 +1265,20 @@ export const serve_rendered = {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (styleJSON.sprite && !httpTester.test(styleJSON.sprite)) {
|
if (styleJSON.sprite && !httpTester.test(styleJSON.sprite)) {
|
||||||
styleJSON.sprite = 'sprites://' +
|
styleJSON.sprite =
|
||||||
|
'sprites://' +
|
||||||
styleJSON.sprite
|
styleJSON.sprite
|
||||||
.replace('{style}', path.basename(styleFile, '.json'))
|
.replace('{style}', path.basename(styleFile, '.json'))
|
||||||
.replace('{styleJsonFolder}', path.relative(options.paths.sprites, path.dirname(styleJSONPath)));
|
.replace(
|
||||||
|
'{styleJsonFolder}',
|
||||||
|
path.relative(options.paths.sprites, path.dirname(styleJSONPath)),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (styleJSON.glyphs && !httpTester.test(styleJSON.glyphs)) {
|
if (styleJSON.glyphs && !httpTester.test(styleJSON.glyphs)) {
|
||||||
styleJSON.glyphs = `fonts://${styleJSON.glyphs}`;
|
styleJSON.glyphs = `fonts://${styleJSON.glyphs}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const layer of (styleJSON.layers || [])) {
|
for (const layer of styleJSON.layers || []) {
|
||||||
if (layer && layer.paint) {
|
if (layer && layer.paint) {
|
||||||
// Remove (flatten) 3D buildings
|
// Remove (flatten) 3D buildings
|
||||||
if (layer.paint['fill-extrusion-height']) {
|
if (layer.paint['fill-extrusion-height']) {
|
||||||
|
@ -1061,14 +1291,14 @@ export const serve_rendered = {
|
||||||
}
|
}
|
||||||
|
|
||||||
const tileJSON = {
|
const tileJSON = {
|
||||||
'tilejson': '2.0.0',
|
tilejson: '2.0.0',
|
||||||
'name': styleJSON.name,
|
name: styleJSON.name,
|
||||||
'attribution': '',
|
attribution: '',
|
||||||
'minzoom': 0,
|
minzoom: 0,
|
||||||
'maxzoom': 20,
|
maxzoom: 20,
|
||||||
'bounds': [-180, -85.0511, 180, 85.0511],
|
bounds: [-180, -85.0511, 180, 85.0511],
|
||||||
'format': 'png',
|
format: 'png',
|
||||||
'type': 'baselayer'
|
type: 'baselayer',
|
||||||
};
|
};
|
||||||
const attributionOverride = params.tilejson && params.tilejson.attribution;
|
const attributionOverride = params.tilejson && params.tilejson.attribution;
|
||||||
Object.assign(tileJSON, params.tilejson || {});
|
Object.assign(tileJSON, params.tilejson || {});
|
||||||
|
@ -1081,7 +1311,7 @@ export const serve_rendered = {
|
||||||
map,
|
map,
|
||||||
dataProjWGStoInternalWGS: null,
|
dataProjWGStoInternalWGS: null,
|
||||||
lastModified: new Date().toUTCString(),
|
lastModified: new Date().toUTCString(),
|
||||||
watermark: params.watermark || options.watermark
|
watermark: params.watermark || options.watermark,
|
||||||
};
|
};
|
||||||
repo[id] = repoobj;
|
repo[id] = repoobj;
|
||||||
|
|
||||||
|
@ -1095,8 +1325,8 @@ export const serve_rendered = {
|
||||||
delete source.url;
|
delete source.url;
|
||||||
|
|
||||||
let mbtilesFile = url.substring('mbtiles://'.length);
|
let mbtilesFile = url.substring('mbtiles://'.length);
|
||||||
const fromData = mbtilesFile[0] === '{' &&
|
const fromData =
|
||||||
mbtilesFile[mbtilesFile.length - 1] === '}';
|
mbtilesFile[0] === '{' && mbtilesFile[mbtilesFile.length - 1] === '}';
|
||||||
|
|
||||||
if (fromData) {
|
if (fromData) {
|
||||||
mbtilesFile = mbtilesFile.substr(1, mbtilesFile.length - 2);
|
mbtilesFile = mbtilesFile.substr(1, mbtilesFile.length - 2);
|
||||||
|
@ -1111,13 +1341,14 @@ export const serve_rendered = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
queue.push(new Promise((resolve, reject) => {
|
queue.push(
|
||||||
|
new Promise((resolve, reject) => {
|
||||||
mbtilesFile = path.resolve(options.paths.mbtiles, mbtilesFile);
|
mbtilesFile = path.resolve(options.paths.mbtiles, mbtilesFile);
|
||||||
const mbtilesFileStats = fs.statSync(mbtilesFile);
|
const mbtilesFileStats = fs.statSync(mbtilesFile);
|
||||||
if (!mbtilesFileStats.isFile() || mbtilesFileStats.size === 0) {
|
if (!mbtilesFileStats.isFile() || mbtilesFileStats.size === 0) {
|
||||||
throw Error(`Not valid MBTiles file: ${mbtilesFile}`);
|
throw Error(`Not valid MBTiles file: ${mbtilesFile}`);
|
||||||
}
|
}
|
||||||
map.sources[name] = new MBTiles(mbtilesFile + '?mode=ro', err => {
|
map.sources[name] = new MBTiles(mbtilesFile + '?mode=ro', (err) => {
|
||||||
map.sources[name].getInfo((err, info) => {
|
map.sources[name].getInfo((err, info) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
@ -1128,7 +1359,8 @@ export const serve_rendered = {
|
||||||
// how to do this for multiple sources with different proj4 defs?
|
// how to do this for multiple sources with different proj4 defs?
|
||||||
const to3857 = proj4('EPSG:3857');
|
const to3857 = proj4('EPSG:3857');
|
||||||
const toDataProj = proj4(info.proj4);
|
const toDataProj = proj4(info.proj4);
|
||||||
repoobj.dataProjWGStoInternalWGS = (xy) => to3857.inverse(toDataProj.forward(xy));
|
repoobj.dataProjWGStoInternalWGS = (xy) =>
|
||||||
|
to3857.inverse(toDataProj.forward(xy));
|
||||||
}
|
}
|
||||||
|
|
||||||
const type = source.type;
|
const type = source.type;
|
||||||
|
@ -1136,7 +1368,7 @@ export const serve_rendered = {
|
||||||
source.type = type;
|
source.type = type;
|
||||||
source.tiles = [
|
source.tiles = [
|
||||||
// meta url which will be detected when requested
|
// meta url which will be detected when requested
|
||||||
`mbtiles://${name}/{z}/{x}/{y}.${info.format || 'pbf'}`
|
`mbtiles://${name}/{z}/{x}/{y}.${info.format || 'pbf'}`,
|
||||||
];
|
];
|
||||||
delete source.scheme;
|
delete source.scheme;
|
||||||
|
|
||||||
|
@ -1144,8 +1376,11 @@ export const serve_rendered = {
|
||||||
source = options.dataDecoratorFunc(name, 'tilejson', source);
|
source = options.dataDecoratorFunc(name, 'tilejson', source);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!attributionOverride &&
|
if (
|
||||||
source.attribution && source.attribution.length > 0) {
|
!attributionOverride &&
|
||||||
|
source.attribution &&
|
||||||
|
source.attribution.length > 0
|
||||||
|
) {
|
||||||
if (!tileJSON.attribution.includes(source.attribution)) {
|
if (!tileJSON.attribution.includes(source.attribution)) {
|
||||||
if (tileJSON.attribution.length > 0) {
|
if (tileJSON.attribution.length > 0) {
|
||||||
tileJSON.attribution += ' | ';
|
tileJSON.attribution += ' | ';
|
||||||
|
@ -1156,7 +1391,8 @@ export const serve_rendered = {
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}));
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1170,7 +1406,12 @@ export const serve_rendered = {
|
||||||
const minPoolSize = minPoolSizes[i];
|
const minPoolSize = minPoolSizes[i];
|
||||||
const maxPoolSize = Math.max(minPoolSize, maxPoolSizes[j]);
|
const maxPoolSize = Math.max(minPoolSize, maxPoolSizes[j]);
|
||||||
map.renderers[s] = createPool(s, 'tile', minPoolSize, maxPoolSize);
|
map.renderers[s] = createPool(s, 'tile', minPoolSize, maxPoolSize);
|
||||||
map.renderers_static[s] = createPool(s, 'static', minPoolSize, maxPoolSize);
|
map.renderers_static[s] = createPool(
|
||||||
|
s,
|
||||||
|
'static',
|
||||||
|
minPoolSize,
|
||||||
|
maxPoolSize,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1187,5 +1428,5 @@ export const serve_rendered = {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
delete repo[id];
|
delete repo[id];
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -12,7 +12,7 @@ import {getPublicUrl} from './utils.js';
|
||||||
const httpTester = /^(http(s)?:)?\/\//;
|
const httpTester = /^(http(s)?:)?\/\//;
|
||||||
|
|
||||||
const fixUrl = (req, url, publicUrl, opt_nokey) => {
|
const fixUrl = (req, url, publicUrl, opt_nokey) => {
|
||||||
if (!url || (typeof url !== 'string') || url.indexOf('local://') !== 0) {
|
if (!url || typeof url !== 'string' || url.indexOf('local://') !== 0) {
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
const queryParams = [];
|
const queryParams = [];
|
||||||
|
@ -23,8 +23,7 @@ const fixUrl = (req, url, publicUrl, opt_nokey) => {
|
||||||
if (queryParams.length) {
|
if (queryParams.length) {
|
||||||
query = `?${queryParams.join('&')}`;
|
query = `?${queryParams.join('&')}`;
|
||||||
}
|
}
|
||||||
return url.replace(
|
return url.replace('local://', getPublicUrl(publicUrl, req)) + query;
|
||||||
'local://', getPublicUrl(publicUrl, req)) + query;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const serve_style = {
|
export const serve_style = {
|
||||||
|
@ -43,10 +42,20 @@ export const serve_style = {
|
||||||
}
|
}
|
||||||
// mapbox-gl-js viewer cannot handle sprite urls with query
|
// mapbox-gl-js viewer cannot handle sprite urls with query
|
||||||
if (styleJSON_.sprite) {
|
if (styleJSON_.sprite) {
|
||||||
styleJSON_.sprite = fixUrl(req, styleJSON_.sprite, item.publicUrl, false);
|
styleJSON_.sprite = fixUrl(
|
||||||
|
req,
|
||||||
|
styleJSON_.sprite,
|
||||||
|
item.publicUrl,
|
||||||
|
false,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (styleJSON_.glyphs) {
|
if (styleJSON_.glyphs) {
|
||||||
styleJSON_.glyphs = fixUrl(req, styleJSON_.glyphs, item.publicUrl, false);
|
styleJSON_.glyphs = fixUrl(
|
||||||
|
req,
|
||||||
|
styleJSON_.glyphs,
|
||||||
|
item.publicUrl,
|
||||||
|
false,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return res.send(styleJSON_);
|
return res.send(styleJSON_);
|
||||||
});
|
});
|
||||||
|
@ -89,7 +98,9 @@ export const serve_style = {
|
||||||
|
|
||||||
const validationErrors = validate(styleFileData);
|
const validationErrors = validate(styleFileData);
|
||||||
if (validationErrors.length > 0) {
|
if (validationErrors.length > 0) {
|
||||||
console.log(`The file "${params.style}" is not valid a valid style file:`);
|
console.log(
|
||||||
|
`The file "${params.style}" is not valid a valid style file:`,
|
||||||
|
);
|
||||||
for (const err of validationErrors) {
|
for (const err of validationErrors) {
|
||||||
console.log(`${err.line}: ${err.message}`);
|
console.log(`${err.line}: ${err.message}`);
|
||||||
}
|
}
|
||||||
|
@ -102,8 +113,8 @@ export const serve_style = {
|
||||||
const url = source.url;
|
const url = source.url;
|
||||||
if (url && url.lastIndexOf('mbtiles:', 0) === 0) {
|
if (url && url.lastIndexOf('mbtiles:', 0) === 0) {
|
||||||
let mbtilesFile = url.substring('mbtiles://'.length);
|
let mbtilesFile = url.substring('mbtiles://'.length);
|
||||||
const fromData = mbtilesFile[0] === '{' &&
|
const fromData =
|
||||||
mbtilesFile[mbtilesFile.length - 1] === '}';
|
mbtilesFile[0] === '{' && mbtilesFile[mbtilesFile.length - 1] === '}';
|
||||||
|
|
||||||
if (fromData) {
|
if (fromData) {
|
||||||
mbtilesFile = mbtilesFile.substr(1, mbtilesFile.length - 2);
|
mbtilesFile = mbtilesFile.substr(1, mbtilesFile.length - 2);
|
||||||
|
@ -135,10 +146,14 @@ export const serve_style = {
|
||||||
let spritePath;
|
let spritePath;
|
||||||
|
|
||||||
if (styleJSON.sprite && !httpTester.test(styleJSON.sprite)) {
|
if (styleJSON.sprite && !httpTester.test(styleJSON.sprite)) {
|
||||||
spritePath = path.join(options.paths.sprites,
|
spritePath = path.join(
|
||||||
|
options.paths.sprites,
|
||||||
styleJSON.sprite
|
styleJSON.sprite
|
||||||
.replace('{style}', path.basename(styleFile, '.json'))
|
.replace('{style}', path.basename(styleFile, '.json'))
|
||||||
.replace('{styleJsonFolder}', path.relative(options.paths.sprites, path.dirname(styleFile)))
|
.replace(
|
||||||
|
'{styleJsonFolder}',
|
||||||
|
path.relative(options.paths.sprites, path.dirname(styleFile)),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
styleJSON.sprite = `local://styles/${id}/sprite`;
|
styleJSON.sprite = `local://styles/${id}/sprite`;
|
||||||
}
|
}
|
||||||
|
@ -150,9 +165,9 @@ export const serve_style = {
|
||||||
styleJSON,
|
styleJSON,
|
||||||
spritePath,
|
spritePath,
|
||||||
publicUrl,
|
publicUrl,
|
||||||
name: styleJSON.name
|
name: styleJSON.name,
|
||||||
};
|
};
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
242
src/server.js
242
src/server.js
|
@ -2,8 +2,7 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
process.env.UV_THREADPOOL_SIZE =
|
process.env.UV_THREADPOOL_SIZE = Math.ceil(Math.max(4, os.cpus().length * 1.5));
|
||||||
Math.ceil(Math.max(4, os.cpus().length * 1.5));
|
|
||||||
|
|
||||||
import fs from 'node:fs';
|
import fs from 'node:fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
@ -25,11 +24,19 @@ import {getTileUrls, getPublicUrl} from './utils.js';
|
||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from 'url';
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
const __dirname = path.dirname(__filename);
|
const __dirname = path.dirname(__filename);
|
||||||
const packageJson = JSON.parse(fs.readFileSync(__dirname + '/../package.json', 'utf8'));
|
const packageJson = JSON.parse(
|
||||||
|
fs.readFileSync(__dirname + '/../package.json', 'utf8'),
|
||||||
|
);
|
||||||
|
|
||||||
const isLight = packageJson.name.slice(-6) === '-light';
|
const isLight = packageJson.name.slice(-6) === '-light';
|
||||||
const serve_rendered = (await import(`${!isLight ? `./serve_rendered.js` : `./serve_light.js`}`)).serve_rendered;
|
const serve_rendered = (
|
||||||
|
await import(`${!isLight ? `./serve_rendered.js` : `./serve_light.js`}`)
|
||||||
|
).serve_rendered;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param opts
|
||||||
|
*/
|
||||||
function start(opts) {
|
function start(opts) {
|
||||||
console.log('Starting server');
|
console.log('Starting server');
|
||||||
|
|
||||||
|
@ -38,18 +45,24 @@ function start(opts) {
|
||||||
styles: {},
|
styles: {},
|
||||||
rendered: {},
|
rendered: {},
|
||||||
data: {},
|
data: {},
|
||||||
fonts: {}
|
fonts: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
app.enable('trust proxy');
|
app.enable('trust proxy');
|
||||||
|
|
||||||
if (process.env.NODE_ENV !== 'test') {
|
if (process.env.NODE_ENV !== 'test') {
|
||||||
const defaultLogFormat = process.env.NODE_ENV === 'production' ? 'tiny' : 'dev';
|
const defaultLogFormat =
|
||||||
|
process.env.NODE_ENV === 'production' ? 'tiny' : 'dev';
|
||||||
const logFormat = opts.logFormat || defaultLogFormat;
|
const logFormat = opts.logFormat || defaultLogFormat;
|
||||||
app.use(morgan(logFormat, {
|
app.use(
|
||||||
stream: opts.logFile ? fs.createWriteStream(opts.logFile, {flags: 'a'}) : process.stdout,
|
morgan(logFormat, {
|
||||||
skip: (req, res) => opts.silent && (res.statusCode === 200 || res.statusCode === 304)
|
stream: opts.logFile
|
||||||
}));
|
? fs.createWriteStream(opts.logFile, { flags: 'a' })
|
||||||
|
: process.stdout,
|
||||||
|
skip: (req, res) =>
|
||||||
|
opts.silent && (res.statusCode === 200 || res.statusCode === 304),
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let config = opts.config || null;
|
let config = opts.config || null;
|
||||||
|
@ -74,7 +87,8 @@ function start(opts) {
|
||||||
options.paths = paths;
|
options.paths = paths;
|
||||||
paths.root = path.resolve(
|
paths.root = path.resolve(
|
||||||
configPath ? path.dirname(configPath) : process.cwd(),
|
configPath ? path.dirname(configPath) : process.cwd(),
|
||||||
paths.root || '');
|
paths.root || '',
|
||||||
|
);
|
||||||
paths.styles = path.resolve(paths.root, paths.styles || '');
|
paths.styles = path.resolve(paths.root, paths.styles || '');
|
||||||
paths.fonts = path.resolve(paths.root, paths.fonts || '');
|
paths.fonts = path.resolve(paths.root, paths.fonts || '');
|
||||||
paths.sprites = path.resolve(paths.root, paths.sprites || '');
|
paths.sprites = path.resolve(paths.root, paths.sprites || '');
|
||||||
|
@ -85,7 +99,9 @@ function start(opts) {
|
||||||
|
|
||||||
const checkPath = (type) => {
|
const checkPath = (type) => {
|
||||||
if (!fs.existsSync(paths[type])) {
|
if (!fs.existsSync(paths[type])) {
|
||||||
console.error(`The specified path for "${type}" does not exist (${paths[type]}).`);
|
console.error(
|
||||||
|
`The specified path for "${type}" does not exist (${paths[type]}).`,
|
||||||
|
);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -98,35 +114,46 @@ function start(opts) {
|
||||||
/**
|
/**
|
||||||
* Recursively get all files within a directory.
|
* Recursively get all files within a directory.
|
||||||
* Inspired by https://stackoverflow.com/a/45130990/10133863
|
* Inspired by https://stackoverflow.com/a/45130990/10133863
|
||||||
* @param {String} directory Absolute path to a directory to get files from.
|
*
|
||||||
|
* @param {string} directory Absolute path to a directory to get files from.
|
||||||
*/
|
*/
|
||||||
const getFiles = async (directory) => {
|
const getFiles = async (directory) => {
|
||||||
// Fetch all entries of the directory and attach type information
|
// Fetch all entries of the directory and attach type information
|
||||||
const dirEntries = await fs.promises.readdir(directory, { withFileTypes: true });
|
const dirEntries = await fs.promises.readdir(directory, {
|
||||||
|
withFileTypes: true,
|
||||||
|
});
|
||||||
|
|
||||||
// Iterate through entries and return the relative file-path to the icon directory if it is not a directory
|
// Iterate through entries and return the relative file-path to the icon directory if it is not a directory
|
||||||
// otherwise initiate a recursive call
|
// otherwise initiate a recursive call
|
||||||
const files = await Promise.all(dirEntries.map((dirEntry) => {
|
const files = await Promise.all(
|
||||||
|
dirEntries.map((dirEntry) => {
|
||||||
const entryPath = path.resolve(directory, dirEntry.name);
|
const entryPath = path.resolve(directory, dirEntry.name);
|
||||||
return dirEntry.isDirectory() ?
|
return dirEntry.isDirectory()
|
||||||
getFiles(entryPath) : entryPath.replace(paths.icons + path.sep, "");
|
? getFiles(entryPath)
|
||||||
}));
|
: entryPath.replace(paths.icons + path.sep, '');
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
// Flatten the list of files to a single array
|
// Flatten the list of files to a single array
|
||||||
return files.flat();
|
return files.flat();
|
||||||
}
|
};
|
||||||
|
|
||||||
// Load all available icons into a settings object
|
// Load all available icons into a settings object
|
||||||
startupPromises.push(new Promise(resolve => {
|
startupPromises.push(
|
||||||
|
new Promise((resolve) => {
|
||||||
getFiles(paths.icons).then((files) => {
|
getFiles(paths.icons).then((files) => {
|
||||||
paths.availableIcons = files;
|
paths.availableIcons = files;
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
}));
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
if (options.dataDecorator) {
|
if (options.dataDecorator) {
|
||||||
try {
|
try {
|
||||||
options.dataDecoratorFunc = require(path.resolve(paths.root, options.dataDecorator));
|
options.dataDecoratorFunc = require(path.resolve(
|
||||||
|
paths.root,
|
||||||
|
options.dataDecorator,
|
||||||
|
));
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -140,17 +167,21 @@ function start(opts) {
|
||||||
app.use('/styles/', serve_style.init(options, serving.styles));
|
app.use('/styles/', serve_style.init(options, serving.styles));
|
||||||
if (!isLight) {
|
if (!isLight) {
|
||||||
startupPromises.push(
|
startupPromises.push(
|
||||||
serve_rendered.init(options, serving.rendered)
|
serve_rendered.init(options, serving.rendered).then((sub) => {
|
||||||
.then((sub) => {
|
|
||||||
app.use('/styles/', sub);
|
app.use('/styles/', sub);
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const addStyle = (id, item, allowMoreData, reportFonts) => {
|
const addStyle = (id, item, allowMoreData, reportFonts) => {
|
||||||
let success = true;
|
let success = true;
|
||||||
if (item.serve_data !== false) {
|
if (item.serve_data !== false) {
|
||||||
success = serve_style.add(options, serving.styles, item, id, opts.publicUrl,
|
success = serve_style.add(
|
||||||
|
options,
|
||||||
|
serving.styles,
|
||||||
|
item,
|
||||||
|
id,
|
||||||
|
opts.publicUrl,
|
||||||
(mbtiles, fromData) => {
|
(mbtiles, fromData) => {
|
||||||
let dataItemId;
|
let dataItemId;
|
||||||
for (const id of Object.keys(data)) {
|
for (const id of Object.keys(data)) {
|
||||||
|
@ -164,30 +195,41 @@ function start(opts) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (dataItemId) { // mbtiles exist in the data config
|
if (dataItemId) {
|
||||||
|
// mbtiles exist in the data config
|
||||||
return dataItemId;
|
return dataItemId;
|
||||||
} else {
|
} else {
|
||||||
if (fromData || !allowMoreData) {
|
if (fromData || !allowMoreData) {
|
||||||
console.log(`ERROR: style "${item.style}" using unknown mbtiles "${mbtiles}"! Skipping...`);
|
console.log(
|
||||||
|
`ERROR: style "${item.style}" using unknown mbtiles "${mbtiles}"! Skipping...`,
|
||||||
|
);
|
||||||
return undefined;
|
return undefined;
|
||||||
} else {
|
} else {
|
||||||
let id = mbtiles.substr(0, mbtiles.lastIndexOf('.')) || mbtiles;
|
let id = mbtiles.substr(0, mbtiles.lastIndexOf('.')) || mbtiles;
|
||||||
while (data[id]) id += '_';
|
while (data[id]) id += '_';
|
||||||
data[id] = {
|
data[id] = {
|
||||||
'mbtiles': mbtiles
|
mbtiles: mbtiles,
|
||||||
};
|
};
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, (font) => {
|
},
|
||||||
|
(font) => {
|
||||||
if (reportFonts) {
|
if (reportFonts) {
|
||||||
serving.fonts[font] = true;
|
serving.fonts[font] = true;
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (success && item.serve_rendered !== false) {
|
if (success && item.serve_rendered !== false) {
|
||||||
if (!isLight) {
|
if (!isLight) {
|
||||||
startupPromises.push(serve_rendered.add(options, serving.rendered, item, id, opts.publicUrl,
|
startupPromises.push(
|
||||||
|
serve_rendered.add(
|
||||||
|
options,
|
||||||
|
serving.rendered,
|
||||||
|
item,
|
||||||
|
id,
|
||||||
|
opts.publicUrl,
|
||||||
(mbtiles) => {
|
(mbtiles) => {
|
||||||
let mbtilesFile;
|
let mbtilesFile;
|
||||||
for (const id of Object.keys(data)) {
|
for (const id of Object.keys(data)) {
|
||||||
|
@ -196,8 +238,9 @@ function start(opts) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return mbtilesFile;
|
return mbtilesFile;
|
||||||
}
|
},
|
||||||
));
|
),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
item.serve_rendered = false;
|
item.serve_rendered = false;
|
||||||
}
|
}
|
||||||
|
@ -217,7 +260,7 @@ function start(opts) {
|
||||||
startupPromises.push(
|
startupPromises.push(
|
||||||
serve_font(options, serving.fonts).then((sub) => {
|
serve_font(options, serving.fonts).then((sub) => {
|
||||||
app.use('/', sub);
|
app.use('/', sub);
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
for (const id of Object.keys(data)) {
|
for (const id of Object.keys(data)) {
|
||||||
|
@ -228,7 +271,7 @@ function start(opts) {
|
||||||
}
|
}
|
||||||
|
|
||||||
startupPromises.push(
|
startupPromises.push(
|
||||||
serve_data.add(options, serving.data, item, id, opts.publicUrl)
|
serve_data.add(options, serving.data, item, id, opts.publicUrl),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -238,22 +281,21 @@ function start(opts) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
if (file.isFile() &&
|
if (file.isFile() && path.extname(file.name).toLowerCase() == '.json') {
|
||||||
path.extname(file.name).toLowerCase() == '.json') {
|
|
||||||
const id = path.basename(file.name, '.json');
|
const id = path.basename(file.name, '.json');
|
||||||
const item = {
|
const item = {
|
||||||
style: file.name
|
style: file.name,
|
||||||
};
|
};
|
||||||
addStyle(id, item, false, false);
|
addStyle(id, item, false, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const watcher = chokidar.watch(path.join(options.paths.styles, '*.json'),
|
const watcher = chokidar.watch(
|
||||||
{
|
path.join(options.paths.styles, '*.json'),
|
||||||
});
|
{},
|
||||||
watcher.on('all',
|
);
|
||||||
(eventType, filename) => {
|
watcher.on('all', (eventType, filename) => {
|
||||||
if (filename) {
|
if (filename) {
|
||||||
const id = path.basename(filename, '.json');
|
const id = path.basename(filename, '.json');
|
||||||
console.log(`Style "${id}" changed, updating...`);
|
console.log(`Style "${id}" changed, updating...`);
|
||||||
|
@ -265,7 +307,7 @@ function start(opts) {
|
||||||
|
|
||||||
if (eventType == 'add' || eventType == 'change') {
|
if (eventType == 'add' || eventType == 'change') {
|
||||||
const item = {
|
const item = {
|
||||||
style: filename
|
style: filename,
|
||||||
};
|
};
|
||||||
addStyle(id, item, false, false);
|
addStyle(id, item, false, false);
|
||||||
}
|
}
|
||||||
|
@ -275,14 +317,19 @@ function start(opts) {
|
||||||
|
|
||||||
app.get('/styles.json', (req, res, next) => {
|
app.get('/styles.json', (req, res, next) => {
|
||||||
const result = [];
|
const result = [];
|
||||||
const query = req.query.key ? (`?key=${encodeURIComponent(req.query.key)}`) : '';
|
const query = req.query.key
|
||||||
|
? `?key=${encodeURIComponent(req.query.key)}`
|
||||||
|
: '';
|
||||||
for (const id of Object.keys(serving.styles)) {
|
for (const id of Object.keys(serving.styles)) {
|
||||||
const styleJSON = serving.styles[id].styleJSON;
|
const styleJSON = serving.styles[id].styleJSON;
|
||||||
result.push({
|
result.push({
|
||||||
version: styleJSON.version,
|
version: styleJSON.version,
|
||||||
name: styleJSON.name,
|
name: styleJSON.name,
|
||||||
id: id,
|
id: id,
|
||||||
url: `${getPublicUrl(opts.publicUrl, req)}styles/${id}/style.json${query}`
|
url: `${getPublicUrl(
|
||||||
|
opts.publicUrl,
|
||||||
|
req,
|
||||||
|
)}styles/${id}/style.json${query}`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
res.send(result);
|
res.send(result);
|
||||||
|
@ -297,9 +344,16 @@ function start(opts) {
|
||||||
} else {
|
} else {
|
||||||
path = `${type}/${id}`;
|
path = `${type}/${id}`;
|
||||||
}
|
}
|
||||||
info.tiles = getTileUrls(req, info.tiles, path, info.format, opts.publicUrl, {
|
info.tiles = getTileUrls(
|
||||||
'pbf': options.pbfAlias
|
req,
|
||||||
});
|
info.tiles,
|
||||||
|
path,
|
||||||
|
info.format,
|
||||||
|
opts.publicUrl,
|
||||||
|
{
|
||||||
|
pbf: options.pbfAlias,
|
||||||
|
},
|
||||||
|
);
|
||||||
arr.push(info);
|
arr.push(info);
|
||||||
}
|
}
|
||||||
return arr;
|
return arr;
|
||||||
|
@ -325,12 +379,15 @@ function start(opts) {
|
||||||
if (template === 'index') {
|
if (template === 'index') {
|
||||||
if (options.frontPage === false) {
|
if (options.frontPage === false) {
|
||||||
return;
|
return;
|
||||||
} else if (options.frontPage &&
|
} else if (
|
||||||
options.frontPage.constructor === String) {
|
options.frontPage &&
|
||||||
|
options.frontPage.constructor === String
|
||||||
|
) {
|
||||||
templateFile = path.resolve(paths.root, options.frontPage);
|
templateFile = path.resolve(paths.root, options.frontPage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
startupPromises.push(new Promise((resolve, reject) => {
|
startupPromises.push(
|
||||||
|
new Promise((resolve, reject) => {
|
||||||
fs.readFile(templateFile, (err, content) => {
|
fs.readFile(templateFile, (err, content) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
err = new Error(`Template not found: ${err.message}`);
|
err = new Error(`Template not found: ${err.message}`);
|
||||||
|
@ -347,18 +404,24 @@ function start(opts) {
|
||||||
return res.status(404).send('Not found');
|
return res.status(404).send('Not found');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
data['server_version'] = `${packageJson.name} v${packageJson.version}`;
|
data[
|
||||||
|
'server_version'
|
||||||
|
] = `${packageJson.name} v${packageJson.version}`;
|
||||||
data['public_url'] = opts.publicUrl || '/';
|
data['public_url'] = opts.publicUrl || '/';
|
||||||
data['is_light'] = isLight;
|
data['is_light'] = isLight;
|
||||||
data['key_query_part'] =
|
data['key_query_part'] = req.query.key
|
||||||
req.query.key ? `key=${encodeURIComponent(req.query.key)}&` : '';
|
? `key=${encodeURIComponent(req.query.key)}&`
|
||||||
data['key_query'] = req.query.key ? `?key=${encodeURIComponent(req.query.key)}` : '';
|
: '';
|
||||||
|
data['key_query'] = req.query.key
|
||||||
|
? `?key=${encodeURIComponent(req.query.key)}`
|
||||||
|
: '';
|
||||||
if (template === 'wmts') res.set('Content-Type', 'text/xml');
|
if (template === 'wmts') res.set('Content-Type', 'text/xml');
|
||||||
return res.status(200).send(compiled(data));
|
return res.status(200).send(compiled(data));
|
||||||
});
|
});
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
}));
|
}),
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
serveTemplate('/$', 'index', (req) => {
|
serveTemplate('/$', 'index', (req) => {
|
||||||
|
@ -371,15 +434,23 @@ function start(opts) {
|
||||||
if (style.serving_rendered) {
|
if (style.serving_rendered) {
|
||||||
const center = style.serving_rendered.tileJSON.center;
|
const center = style.serving_rendered.tileJSON.center;
|
||||||
if (center) {
|
if (center) {
|
||||||
style.viewer_hash = `#${center[2]}/${center[1].toFixed(5)}/${center[0].toFixed(5)}`;
|
style.viewer_hash = `#${center[2]}/${center[1].toFixed(
|
||||||
|
5,
|
||||||
|
)}/${center[0].toFixed(5)}`;
|
||||||
|
|
||||||
const centerPx = mercator.px([center[0], center[1]], center[2]);
|
const centerPx = mercator.px([center[0], center[1]], center[2]);
|
||||||
style.thumbnail = `${center[2]}/${Math.floor(centerPx[0] / 256)}/${Math.floor(centerPx[1] / 256)}.png`;
|
style.thumbnail = `${center[2]}/${Math.floor(
|
||||||
|
centerPx[0] / 256,
|
||||||
|
)}/${Math.floor(centerPx[1] / 256)}.png`;
|
||||||
}
|
}
|
||||||
|
|
||||||
style.xyz_link = getTileUrls(
|
style.xyz_link = getTileUrls(
|
||||||
req, style.serving_rendered.tileJSON.tiles,
|
req,
|
||||||
`styles/${id}`, style.serving_rendered.tileJSON.format, opts.publicUrl)[0];
|
style.serving_rendered.tileJSON.tiles,
|
||||||
|
`styles/${id}`,
|
||||||
|
style.serving_rendered.tileJSON.format,
|
||||||
|
opts.publicUrl,
|
||||||
|
)[0];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const data = clone(serving.data || {});
|
const data = clone(serving.data || {});
|
||||||
|
@ -388,19 +459,29 @@ function start(opts) {
|
||||||
const tilejson = data[id].tileJSON;
|
const tilejson = data[id].tileJSON;
|
||||||
const center = tilejson.center;
|
const center = tilejson.center;
|
||||||
if (center) {
|
if (center) {
|
||||||
data_.viewer_hash = `#${center[2]}/${center[1].toFixed(5)}/${center[0].toFixed(5)}`;
|
data_.viewer_hash = `#${center[2]}/${center[1].toFixed(
|
||||||
|
5,
|
||||||
|
)}/${center[0].toFixed(5)}`;
|
||||||
}
|
}
|
||||||
data_.is_vector = tilejson.format === 'pbf';
|
data_.is_vector = tilejson.format === 'pbf';
|
||||||
if (!data_.is_vector) {
|
if (!data_.is_vector) {
|
||||||
if (center) {
|
if (center) {
|
||||||
const centerPx = mercator.px([center[0], center[1]], center[2]);
|
const centerPx = mercator.px([center[0], center[1]], center[2]);
|
||||||
data_.thumbnail = `${center[2]}/${Math.floor(centerPx[0] / 256)}/${Math.floor(centerPx[1] / 256)}.${data_.tileJSON.format}`;
|
data_.thumbnail = `${center[2]}/${Math.floor(
|
||||||
|
centerPx[0] / 256,
|
||||||
|
)}/${Math.floor(centerPx[1] / 256)}.${data_.tileJSON.format}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
data_.xyz_link = getTileUrls(
|
data_.xyz_link = getTileUrls(
|
||||||
req, tilejson.tiles, `data/${id}`, tilejson.format, opts.publicUrl, {
|
req,
|
||||||
'pbf': options.pbfAlias
|
tilejson.tiles,
|
||||||
})[0];
|
`data/${id}`,
|
||||||
|
tilejson.format,
|
||||||
|
opts.publicUrl,
|
||||||
|
{
|
||||||
|
pbf: options.pbfAlias,
|
||||||
|
},
|
||||||
|
)[0];
|
||||||
}
|
}
|
||||||
if (data_.filesize) {
|
if (data_.filesize) {
|
||||||
let suffix = 'kB';
|
let suffix = 'kB';
|
||||||
|
@ -418,7 +499,7 @@ function start(opts) {
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
styles: Object.keys(styles).length ? styles : null,
|
styles: Object.keys(styles).length ? styles : null,
|
||||||
data: Object.keys(data).length ? data : null
|
data: Object.keys(data).length ? data : null,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -453,9 +534,12 @@ function start(opts) {
|
||||||
wmts.name = (serving.styles[id] || serving.rendered[id]).name;
|
wmts.name = (serving.styles[id] || serving.rendered[id]).name;
|
||||||
if (opts.publicUrl) {
|
if (opts.publicUrl) {
|
||||||
wmts.baseUrl = opts.publicUrl;
|
wmts.baseUrl = opts.publicUrl;
|
||||||
}
|
} else {
|
||||||
else {
|
wmts.baseUrl = `${
|
||||||
wmts.baseUrl = `${req.get('X-Forwarded-Protocol') ? req.get('X-Forwarded-Protocol') : req.protocol}://${req.get('host')}/`;
|
req.get('X-Forwarded-Protocol')
|
||||||
|
? req.get('X-Forwarded-Protocol')
|
||||||
|
: req.protocol
|
||||||
|
}://${req.get('host')}/`;
|
||||||
}
|
}
|
||||||
return wmts;
|
return wmts;
|
||||||
});
|
});
|
||||||
|
@ -484,13 +568,17 @@ function start(opts) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const server = app.listen(process.env.PORT || opts.port, process.env.BIND || opts.bind, function() {
|
const server = app.listen(
|
||||||
|
process.env.PORT || opts.port,
|
||||||
|
process.env.BIND || opts.bind,
|
||||||
|
function () {
|
||||||
let address = this.address().address;
|
let address = this.address().address;
|
||||||
if (address.indexOf('::') === 0) {
|
if (address.indexOf('::') === 0) {
|
||||||
address = `[${address}]`; // literal IPv6 address
|
address = `[${address}]`; // literal IPv6 address
|
||||||
}
|
}
|
||||||
console.log(`Listening at http://${address}:${this.address().port}/`);
|
console.log(`Listening at http://${address}:${this.address().port}/`);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
// add server.shutdown() to gracefully stop serving
|
// add server.shutdown() to gracefully stop serving
|
||||||
enableShutdown(server);
|
enableShutdown(server);
|
||||||
|
@ -498,10 +586,14 @@ function start(opts) {
|
||||||
return {
|
return {
|
||||||
app: app,
|
app: app,
|
||||||
server: server,
|
server: server,
|
||||||
startupPromise: startupPromise
|
startupPromise: startupPromise,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param opts
|
||||||
|
*/
|
||||||
export function server(opts) {
|
export function server(opts) {
|
||||||
const running = start(opts);
|
const running = start(opts);
|
||||||
|
|
||||||
|
@ -525,4 +617,4 @@ export function server(opts) {
|
||||||
});
|
});
|
||||||
|
|
||||||
return running;
|
return running;
|
||||||
};
|
}
|
||||||
|
|
41
src/utils.js
41
src/utils.js
|
@ -6,8 +6,8 @@ import fs from 'node:fs';
|
||||||
import clone from 'clone';
|
import clone from 'clone';
|
||||||
import glyphCompose from '@mapbox/glyph-pbf-composite';
|
import glyphCompose from '@mapbox/glyph-pbf-composite';
|
||||||
|
|
||||||
|
export const getPublicUrl = (publicUrl, req) =>
|
||||||
export const getPublicUrl = (publicUrl, req) => publicUrl || `${req.protocol}://${req.headers.host}/`;
|
publicUrl || `${req.protocol}://${req.headers.host}/`;
|
||||||
|
|
||||||
export const getTileUrls = (req, domains, path, format, publicUrl, aliases) => {
|
export const getTileUrls = (req, domains, path, format, publicUrl, aliases) => {
|
||||||
if (domains) {
|
if (domains) {
|
||||||
|
@ -16,7 +16,8 @@ export const getTileUrls = (req, domains, path, format, publicUrl, aliases) => {
|
||||||
}
|
}
|
||||||
const host = req.headers.host;
|
const host = req.headers.host;
|
||||||
const hostParts = host.split('.');
|
const hostParts = host.split('.');
|
||||||
const relativeSubdomainsUsable = hostParts.length > 1 &&
|
const relativeSubdomainsUsable =
|
||||||
|
hostParts.length > 1 &&
|
||||||
!/^([0-9]{1,3}\.){3}[0-9]{1,3}(\:[0-9]+)?$/.test(host);
|
!/^([0-9]{1,3}\.){3}[0-9]{1,3}(\:[0-9]+)?$/.test(host);
|
||||||
const newDomains = [];
|
const newDomains = [];
|
||||||
for (const domain of domains) {
|
for (const domain of domains) {
|
||||||
|
@ -43,7 +44,7 @@ export const getTileUrls = (req, domains, path, format, publicUrl, aliases) => {
|
||||||
if (req.query.style) {
|
if (req.query.style) {
|
||||||
queryParams.push(`style=${encodeURIComponent(req.query.style)}`);
|
queryParams.push(`style=${encodeURIComponent(req.query.style)}`);
|
||||||
}
|
}
|
||||||
const query = queryParams.length > 0 ? (`?${queryParams.join('&')}`) : '';
|
const query = queryParams.length > 0 ? `?${queryParams.join('&')}` : '';
|
||||||
|
|
||||||
if (aliases && aliases[format]) {
|
if (aliases && aliases[format]) {
|
||||||
format = aliases[format];
|
format = aliases[format];
|
||||||
|
@ -52,7 +53,9 @@ export const getTileUrls = (req, domains, path, format, publicUrl, aliases) => {
|
||||||
const uris = [];
|
const uris = [];
|
||||||
if (!publicUrl) {
|
if (!publicUrl) {
|
||||||
for (const domain of domains) {
|
for (const domain of domains) {
|
||||||
uris.push(`${req.protocol}://${domain}/${path}/{z}/{x}/{y}.${format}${query}`);
|
uris.push(
|
||||||
|
`${req.protocol}://${domain}/${path}/{z}/{x}/{y}.${format}${query}`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
uris.push(`${publicUrl}${path}/{z}/{x}/{y}.${format}${query}`);
|
uris.push(`${publicUrl}${path}/{z}/{x}/{y}.${format}${query}`);
|
||||||
|
@ -70,13 +73,14 @@ export const fixTileJSONCenter = (tileJSON) => {
|
||||||
(tileJSON.bounds[1] + tileJSON.bounds[3]) / 2,
|
(tileJSON.bounds[1] + tileJSON.bounds[3]) / 2,
|
||||||
Math.round(
|
Math.round(
|
||||||
-Math.log((tileJSON.bounds[2] - tileJSON.bounds[0]) / 360 / tiles) /
|
-Math.log((tileJSON.bounds[2] - tileJSON.bounds[0]) / 360 / tiles) /
|
||||||
Math.LN2
|
Math.LN2,
|
||||||
)
|
),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getFontPbf = (allowedFonts, fontPath, name, range, fallbacks) => new Promise((resolve, reject) => {
|
const getFontPbf = (allowedFonts, fontPath, name, range, fallbacks) =>
|
||||||
|
new Promise((resolve, reject) => {
|
||||||
if (!allowedFonts || (allowedFonts[name] && fallbacks)) {
|
if (!allowedFonts || (allowedFonts[name] && fallbacks)) {
|
||||||
const filename = path.join(fontPath, name, `${range}.pbf`);
|
const filename = path.join(fontPath, name, `${range}.pbf`);
|
||||||
if (!fallbacks) {
|
if (!fallbacks) {
|
||||||
|
@ -103,7 +107,10 @@ const getFontPbf = (allowedFonts, fontPath, name, range, fallbacks) => new Promi
|
||||||
|
|
||||||
console.error(`ERROR: Trying to use ${fallbackName} as a fallback`);
|
console.error(`ERROR: Trying to use ${fallbackName} as a fallback`);
|
||||||
delete fallbacks[fallbackName];
|
delete fallbacks[fallbackName];
|
||||||
getFontPbf(null, fontPath, fallbackName, range, fallbacks).then(resolve, reject);
|
getFontPbf(null, fontPath, fallbackName, range, fallbacks).then(
|
||||||
|
resolve,
|
||||||
|
reject,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
reject(`Font load error: ${name}`);
|
reject(`Font load error: ${name}`);
|
||||||
}
|
}
|
||||||
|
@ -116,12 +123,24 @@ const getFontPbf = (allowedFonts, fontPath, name, range, fallbacks) => new Promi
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export const getFontsPbf = (allowedFonts, fontPath, names, range, fallbacks) => {
|
export const getFontsPbf = (
|
||||||
|
allowedFonts,
|
||||||
|
fontPath,
|
||||||
|
names,
|
||||||
|
range,
|
||||||
|
fallbacks,
|
||||||
|
) => {
|
||||||
const fonts = names.split(',');
|
const fonts = names.split(',');
|
||||||
const queue = [];
|
const queue = [];
|
||||||
for (const font of fonts) {
|
for (const font of fonts) {
|
||||||
queue.push(
|
queue.push(
|
||||||
getFontPbf(allowedFonts, fontPath, font, range, clone(allowedFonts || fallbacks))
|
getFontPbf(
|
||||||
|
allowedFonts,
|
||||||
|
fontPath,
|
||||||
|
font,
|
||||||
|
range,
|
||||||
|
clone(allowedFonts || fallbacks),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,8 @@ const testTileJSONArray = function(url) {
|
||||||
.expect(function (res) {
|
.expect(function (res) {
|
||||||
expect(res.body).to.be.a('array');
|
expect(res.body).to.be.a('array');
|
||||||
expect(res.body.length).to.be.greaterThan(0);
|
expect(res.body.length).to.be.greaterThan(0);
|
||||||
}).end(done);
|
})
|
||||||
|
.end(done);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -32,7 +33,8 @@ const testTileJSON = function(url) {
|
||||||
.get(url)
|
.get(url)
|
||||||
.expect(function (res) {
|
.expect(function (res) {
|
||||||
expect(res.body.tiles.length).to.be.greaterThan(0);
|
expect(res.body.tiles.length).to.be.greaterThan(0);
|
||||||
}).end(done);
|
})
|
||||||
|
.end(done);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -40,9 +42,7 @@ const testTileJSON = function(url) {
|
||||||
describe('Metadata', function () {
|
describe('Metadata', function () {
|
||||||
describe('/health', function () {
|
describe('/health', function () {
|
||||||
it('returns 200', function (done) {
|
it('returns 200', function (done) {
|
||||||
supertest(app)
|
supertest(app).get('/health').expect(200, done);
|
||||||
.get('/health')
|
|
||||||
.expect(200, done);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -67,7 +67,8 @@ describe('Metadata', function() {
|
||||||
expect(res.body[0].version).to.be.equal(8);
|
expect(res.body[0].version).to.be.equal(8);
|
||||||
expect(res.body[0].id).to.be.a('string');
|
expect(res.body[0].id).to.be.a('string');
|
||||||
expect(res.body[0].name).to.be.a('string');
|
expect(res.body[0].name).to.be.a('string');
|
||||||
}).end(done);
|
})
|
||||||
|
.end(done);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ before(function() {
|
||||||
const running = server({
|
const running = server({
|
||||||
configPath: 'config.json',
|
configPath: 'config.json',
|
||||||
port: 8888,
|
port: 8888,
|
||||||
publicUrl: '/test/'
|
publicUrl: '/test/',
|
||||||
});
|
});
|
||||||
global.app = running.app;
|
global.app = running.app;
|
||||||
global.server = running.server;
|
global.server = running.server;
|
||||||
|
@ -23,6 +23,7 @@ before(function() {
|
||||||
after(function () {
|
after(function () {
|
||||||
console.log('global teardown');
|
console.log('global teardown');
|
||||||
global.server.close(function () {
|
global.server.close(function () {
|
||||||
console.log('Done'); process.exit();
|
console.log('Done');
|
||||||
|
process.exit();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
122
test/static.js
122
test/static.js
|
@ -18,10 +18,38 @@ describe('Static endpoints', function() {
|
||||||
describe('center-based', function () {
|
describe('center-based', function () {
|
||||||
describe('valid requests', function () {
|
describe('valid requests', function () {
|
||||||
describe('various formats', function () {
|
describe('various formats', function () {
|
||||||
testStatic(prefix, '0,0,0/256x256', 'png', 200, undefined, /image\/png/);
|
testStatic(
|
||||||
testStatic(prefix, '0,0,0/256x256', 'jpg', 200, undefined, /image\/jpeg/);
|
prefix,
|
||||||
testStatic(prefix, '0,0,0/256x256', 'jpeg', 200, undefined, /image\/jpeg/);
|
'0,0,0/256x256',
|
||||||
testStatic(prefix, '0,0,0/256x256', 'webp', 200, undefined, /image\/webp/);
|
'png',
|
||||||
|
200,
|
||||||
|
undefined,
|
||||||
|
/image\/png/,
|
||||||
|
);
|
||||||
|
testStatic(
|
||||||
|
prefix,
|
||||||
|
'0,0,0/256x256',
|
||||||
|
'jpg',
|
||||||
|
200,
|
||||||
|
undefined,
|
||||||
|
/image\/jpeg/,
|
||||||
|
);
|
||||||
|
testStatic(
|
||||||
|
prefix,
|
||||||
|
'0,0,0/256x256',
|
||||||
|
'jpeg',
|
||||||
|
200,
|
||||||
|
undefined,
|
||||||
|
/image\/jpeg/,
|
||||||
|
);
|
||||||
|
testStatic(
|
||||||
|
prefix,
|
||||||
|
'0,0,0/256x256',
|
||||||
|
'webp',
|
||||||
|
200,
|
||||||
|
undefined,
|
||||||
|
/image\/webp/,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('different parameters', function () {
|
describe('different parameters', function () {
|
||||||
|
@ -60,10 +88,38 @@ describe('Static endpoints', function() {
|
||||||
describe('area-based', function () {
|
describe('area-based', function () {
|
||||||
describe('valid requests', function () {
|
describe('valid requests', function () {
|
||||||
describe('various formats', function () {
|
describe('various formats', function () {
|
||||||
testStatic(prefix, '-180,-80,180,80/10x10', 'png', 200, undefined, /image\/png/);
|
testStatic(
|
||||||
testStatic(prefix, '-180,-80,180,80/10x10', 'jpg', 200, undefined, /image\/jpeg/);
|
prefix,
|
||||||
testStatic(prefix, '-180,-80,180,80/10x10', 'jpeg', 200, undefined, /image\/jpeg/);
|
'-180,-80,180,80/10x10',
|
||||||
testStatic(prefix, '-180,-80,180,80/10x10', 'webp', 200, undefined, /image\/webp/);
|
'png',
|
||||||
|
200,
|
||||||
|
undefined,
|
||||||
|
/image\/png/,
|
||||||
|
);
|
||||||
|
testStatic(
|
||||||
|
prefix,
|
||||||
|
'-180,-80,180,80/10x10',
|
||||||
|
'jpg',
|
||||||
|
200,
|
||||||
|
undefined,
|
||||||
|
/image\/jpeg/,
|
||||||
|
);
|
||||||
|
testStatic(
|
||||||
|
prefix,
|
||||||
|
'-180,-80,180,80/10x10',
|
||||||
|
'jpeg',
|
||||||
|
200,
|
||||||
|
undefined,
|
||||||
|
/image\/jpeg/,
|
||||||
|
);
|
||||||
|
testStatic(
|
||||||
|
prefix,
|
||||||
|
'-180,-80,180,80/10x10',
|
||||||
|
'webp',
|
||||||
|
200,
|
||||||
|
undefined,
|
||||||
|
/image\/webp/,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('different parameters', function () {
|
describe('different parameters', function () {
|
||||||
|
@ -85,18 +141,58 @@ describe('Static endpoints', function() {
|
||||||
|
|
||||||
describe('autofit path', function () {
|
describe('autofit path', function () {
|
||||||
describe('valid requests', function () {
|
describe('valid requests', function () {
|
||||||
testStatic(prefix, 'auto/256x256', 'png', 200, undefined, /image\/png/, '?path=10,10|20,20');
|
testStatic(
|
||||||
|
prefix,
|
||||||
|
'auto/256x256',
|
||||||
|
'png',
|
||||||
|
200,
|
||||||
|
undefined,
|
||||||
|
/image\/png/,
|
||||||
|
'?path=10,10|20,20',
|
||||||
|
);
|
||||||
|
|
||||||
describe('different parameters', function () {
|
describe('different parameters', function () {
|
||||||
testStatic(prefix, 'auto/20x20', 'png', 200, 2, /image\/png/, '?path=10,10|20,20');
|
testStatic(
|
||||||
testStatic(prefix, 'auto/200x200', 'png', 200, 3, /image\/png/, '?path=-10,-10|-20,-20');
|
prefix,
|
||||||
|
'auto/20x20',
|
||||||
|
'png',
|
||||||
|
200,
|
||||||
|
2,
|
||||||
|
/image\/png/,
|
||||||
|
'?path=10,10|20,20',
|
||||||
|
);
|
||||||
|
testStatic(
|
||||||
|
prefix,
|
||||||
|
'auto/200x200',
|
||||||
|
'png',
|
||||||
|
200,
|
||||||
|
3,
|
||||||
|
/image\/png/,
|
||||||
|
'?path=-10,-10|-20,-20',
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('invalid requests return 4xx', function () {
|
describe('invalid requests return 4xx', function () {
|
||||||
testStatic(prefix, 'auto/256x256', 'png', 400);
|
testStatic(prefix, 'auto/256x256', 'png', 400);
|
||||||
testStatic(prefix, 'auto/256x256', 'png', 400, undefined, undefined, '?path=invalid');
|
testStatic(
|
||||||
testStatic(prefix, 'auto/2560x2560', 'png', 400, undefined, undefined, '?path=10,10|20,20');
|
prefix,
|
||||||
|
'auto/256x256',
|
||||||
|
'png',
|
||||||
|
400,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
'?path=invalid',
|
||||||
|
);
|
||||||
|
testStatic(
|
||||||
|
prefix,
|
||||||
|
'auto/2560x2560',
|
||||||
|
'png',
|
||||||
|
400,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
'?path=10,10|20,20',
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
const testIs = function (url, type, status) {
|
const testIs = function (url, type, status) {
|
||||||
it(url + ' return ' + (status || 200) + ' and is ' + type.toString(),
|
it(
|
||||||
|
url + ' return ' + (status || 200) + ' and is ' + type.toString(),
|
||||||
function (done) {
|
function (done) {
|
||||||
supertest(app)
|
supertest(app)
|
||||||
.get(url)
|
.get(url)
|
||||||
.expect(status || 200)
|
.expect(status || 200)
|
||||||
.expect('Content-Type', type, done);
|
.expect('Content-Type', type, done);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const prefix = 'test-style';
|
const prefix = 'test-style';
|
||||||
|
@ -25,7 +27,8 @@ describe('Styles', function() {
|
||||||
expect(res.body.sprite).to.be.a('string');
|
expect(res.body.sprite).to.be.a('string');
|
||||||
expect(res.body.sprite).to.be.equal('/test/styles/test-style/sprite');
|
expect(res.body.sprite).to.be.equal('/test/styles/test-style/sprite');
|
||||||
expect(res.body.layers).to.be.a('array');
|
expect(res.body.layers).to.be.a('array');
|
||||||
}).end(done);
|
})
|
||||||
|
.end(done);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('/styles/streets/style.json is not served', function () {
|
describe('/styles/streets/style.json is not served', function () {
|
||||||
|
@ -43,8 +46,10 @@ describe('Styles', function() {
|
||||||
describe('Fonts', function () {
|
describe('Fonts', function () {
|
||||||
testIs('/fonts/Open Sans Bold/0-255.pbf', /application\/x-protobuf/);
|
testIs('/fonts/Open Sans Bold/0-255.pbf', /application\/x-protobuf/);
|
||||||
testIs('/fonts/Open Sans Regular/65280-65535.pbf', /application\/x-protobuf/);
|
testIs('/fonts/Open Sans Regular/65280-65535.pbf', /application\/x-protobuf/);
|
||||||
testIs('/fonts/Open Sans Bold,Open Sans Regular/0-255.pbf',
|
testIs(
|
||||||
/application\/x-protobuf/);
|
'/fonts/Open Sans Bold,Open Sans Regular/0-255.pbf',
|
||||||
|
/application\/x-protobuf/,
|
||||||
|
);
|
||||||
testIs('/fonts/Nonsense,Open Sans Bold/0-255.pbf', /./, 400);
|
testIs('/fonts/Nonsense,Open Sans Bold/0-255.pbf', /./, 400);
|
||||||
|
|
||||||
testIs('/fonts/Nonsense/0-255.pbf', /./, 400);
|
testIs('/fonts/Nonsense/0-255.pbf', /./, 400);
|
||||||
|
|
Loading…
Reference in a new issue