style: lint fix all file(s)
Signed-off-by: Vinayak Kulkarni <19776877+vinayakkulkarni@users.noreply.github.com>
This commit is contained in:
parent
837b664a7f
commit
b46a7db1bd
15 changed files with 1099 additions and 663 deletions
21
publish.js
21
publish.js
|
|
@ -12,18 +12,23 @@
|
|||
|
||||
// SYNC THE `light` FOLDER
|
||||
|
||||
import child_process from 'child_process'
|
||||
child_process.execSync('rsync -av --exclude="light" --exclude=".git" --exclude="node_modules" --delete . light', {
|
||||
stdio: 'inherit'
|
||||
});
|
||||
import child_process from 'child_process';
|
||||
child_process.execSync(
|
||||
'rsync -av --exclude="light" --exclude=".git" --exclude="node_modules" --delete . light',
|
||||
{
|
||||
stdio: 'inherit',
|
||||
},
|
||||
);
|
||||
|
||||
// PATCH `package.json`
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import {fileURLToPath} from 'url';
|
||||
import { fileURLToPath } from 'url';
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
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.description =
|
||||
|
|
@ -52,10 +57,10 @@ if (process.argv.length > 2 && process.argv[2] == '--no-publish') {
|
|||
|
||||
// tileserver-gl
|
||||
child_process.execSync('npm publish . --access public', {
|
||||
stdio: 'inherit'
|
||||
stdio: 'inherit',
|
||||
});
|
||||
|
||||
// tileserver-gl-light
|
||||
child_process.execSync('npm publish ./light --access public', {
|
||||
stdio: 'inherit'
|
||||
stdio: 'inherit',
|
||||
});
|
||||
|
|
|
|||
124
src/main.js
124
src/main.js
|
|
@ -4,73 +4,52 @@
|
|||
|
||||
import fs from 'node:fs';
|
||||
import path from 'path';
|
||||
import {fileURLToPath} from 'url';
|
||||
import { fileURLToPath } from 'url';
|
||||
import request from 'request';
|
||||
import {server} from './server.js';
|
||||
import { server } from './server.js';
|
||||
|
||||
import MBTiles from '@mapbox/mbtiles';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
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;
|
||||
if (args.length >= 3 && args[2][0] !== '-') {
|
||||
args.splice(2, 0, '--mbtiles');
|
||||
}
|
||||
|
||||
import {program} from 'commander';
|
||||
import { program } from 'commander';
|
||||
program
|
||||
.description('tileserver-gl startup options')
|
||||
.usage('tileserver-gl [mbtiles] [options]')
|
||||
.option(
|
||||
'--mbtiles <file>',
|
||||
'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(
|
||||
'-c, --config <file>',
|
||||
'Configuration file [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'
|
||||
'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(
|
||||
'-u|--public_url <url>',
|
||||
'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)'
|
||||
'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(
|
||||
'-f|--log_format <format>',
|
||||
'define the log format: https://github.com/expressjs/morgan#morganformat-options'
|
||||
)
|
||||
.version(
|
||||
packageJson.version,
|
||||
'-v, --version'
|
||||
'define the log format: https://github.com/expressjs/morgan#morganformat-options',
|
||||
)
|
||||
.version(packageJson.version, '-v, --version');
|
||||
program.parse(process.argv);
|
||||
const opts = program.opts();
|
||||
|
||||
|
|
@ -91,14 +70,16 @@ const startServer = (configPath, config) => {
|
|||
silent: opts.silent,
|
||||
logFile: opts.log_file,
|
||||
logFormat: opts.log_format,
|
||||
publicUrl: publicUrl
|
||||
publicUrl: publicUrl,
|
||||
});
|
||||
};
|
||||
|
||||
const startWithMBTiles = (mbtilesFile) => {
|
||||
console.log(`[INFO] Automatically creating config file for ${mbtilesFile}`);
|
||||
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);
|
||||
|
||||
|
|
@ -110,60 +91,72 @@ const startWithMBTiles = (mbtilesFile) => {
|
|||
const instance = new MBTiles(mbtilesFile + '?mode=ro', (err) => {
|
||||
if (err) {
|
||||
console.log('ERROR: Unable to open MBTiles.');
|
||||
console.log(` Make sure ${path.basename(mbtilesFile)} is valid MBTiles.`);
|
||||
console.log(
|
||||
` Make sure ${path.basename(mbtilesFile)} is valid MBTiles.`,
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
instance.getInfo((err, info) => {
|
||||
if (err || !info) {
|
||||
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);
|
||||
}
|
||||
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 = {
|
||||
'options': {
|
||||
'paths': {
|
||||
'root': styleDir,
|
||||
'fonts': 'fonts',
|
||||
'styles': 'styles',
|
||||
'mbtiles': path.dirname(mbtilesFile)
|
||||
}
|
||||
options: {
|
||||
paths: {
|
||||
root: styleDir,
|
||||
fonts: 'fonts',
|
||||
styles: 'styles',
|
||||
mbtiles: path.dirname(mbtilesFile),
|
||||
},
|
||||
},
|
||||
'styles': {},
|
||||
'data': {}
|
||||
styles: {},
|
||||
data: {},
|
||||
};
|
||||
|
||||
if (info.format === 'pbf' &&
|
||||
info.name.toLowerCase().indexOf('openmaptiles') > -1) {
|
||||
if (
|
||||
info.format === 'pbf' &&
|
||||
info.name.toLowerCase().indexOf('openmaptiles') > -1
|
||||
) {
|
||||
config['data'][`v3`] = {
|
||||
'mbtiles': path.basename(mbtilesFile)
|
||||
mbtiles: path.basename(mbtilesFile),
|
||||
};
|
||||
|
||||
|
||||
const styles = fs.readdirSync(path.resolve(styleDir, 'styles'));
|
||||
for (const styleName of styles) {
|
||||
const styleFileRel = styleName + '/style.json';
|
||||
const styleFile = path.resolve(styleDir, 'styles', styleFileRel);
|
||||
if (fs.existsSync(styleFile)) {
|
||||
config['styles'][styleName] = {
|
||||
'style': styleFileRel,
|
||||
'tilejson': {
|
||||
'bounds': bounds
|
||||
}
|
||||
style: styleFileRel,
|
||||
tilejson: {
|
||||
bounds: bounds,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.log(`WARN: MBTiles not in "openmaptiles" format. Serving raw data only...`);
|
||||
config['data'][(info.id || 'mbtiles')
|
||||
console.log(
|
||||
`WARN: MBTiles not in "openmaptiles" format. Serving raw data only...`,
|
||||
);
|
||||
config['data'][
|
||||
(info.id || 'mbtiles')
|
||||
.replace(/\//g, '_')
|
||||
.replace(/:/g, '_')
|
||||
.replace(/\?/g, '_')] = {
|
||||
'mbtiles': path.basename(mbtilesFile)
|
||||
.replace(/\?/g, '_')
|
||||
] = {
|
||||
mbtiles: path.basename(mbtilesFile),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -197,7 +190,8 @@ fs.stat(path.resolve(opts.config), (err, stats) => {
|
|||
console.log(`No MBTiles specified, using ${mbtiles}`);
|
||||
return startWithMBTiles(mbtiles);
|
||||
} 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 stream = fs.createWriteStream(filename);
|
||||
console.log(`No MBTiles found`);
|
||||
|
|
|
|||
|
|
@ -10,30 +10,38 @@ import MBTiles from '@mapbox/mbtiles';
|
|||
import Pbf from 'pbf';
|
||||
import VectorTile from '@mapbox/vector-tile';
|
||||
|
||||
import {getTileUrls, fixTileJSONCenter} from './utils.js';
|
||||
import { getTileUrls, fixTileJSONCenter } from './utils.js';
|
||||
|
||||
export const serve_data = {
|
||||
init: (options, repo) => {
|
||||
const app = express().disable('x-powered-by');
|
||||
|
||||
app.get('/:id/:z(\\d+)/:x(\\d+)/:y(\\d+).:format([\\w.]+)', (req, res, next) => {
|
||||
const item = repo[req.params.id];
|
||||
if (!item) {
|
||||
return res.sendStatus(404);
|
||||
}
|
||||
const tileJSONFormat = item.tileJSON.format;
|
||||
const z = req.params.z | 0;
|
||||
const x = req.params.x | 0;
|
||||
const y = req.params.y | 0;
|
||||
let format = req.params.format;
|
||||
if (format === options.pbfAlias) {
|
||||
format = 'pbf';
|
||||
}
|
||||
if (format !== tileJSONFormat &&
|
||||
!(format === 'geojson' && tileJSONFormat === 'pbf')) {
|
||||
return res.status(404).send('Invalid format');
|
||||
}
|
||||
if (z < item.tileJSON.minzoom || 0 || x < 0 || y < 0 ||
|
||||
app.get(
|
||||
'/:id/:z(\\d+)/:x(\\d+)/:y(\\d+).:format([\\w.]+)',
|
||||
(req, res, next) => {
|
||||
const item = repo[req.params.id];
|
||||
if (!item) {
|
||||
return res.sendStatus(404);
|
||||
}
|
||||
const tileJSONFormat = item.tileJSON.format;
|
||||
const z = req.params.z | 0;
|
||||
const x = req.params.x | 0;
|
||||
const y = req.params.y | 0;
|
||||
let format = req.params.format;
|
||||
if (format === options.pbfAlias) {
|
||||
format = 'pbf';
|
||||
}
|
||||
if (
|
||||
format !== tileJSONFormat &&
|
||||
!(format === 'geojson' && tileJSONFormat === 'pbf')
|
||||
) {
|
||||
return res.status(404).send('Invalid format');
|
||||
}
|
||||
if (
|
||||
z < item.tileJSON.minzoom ||
|
||||
0 ||
|
||||
x < 0 ||
|
||||
y < 0 ||
|
||||
z > item.tileJSON.maxzoom ||
|
||||
x >= Math.pow(2, z) ||
|
||||
y >= Math.pow(2, z)
|
||||
|
|
@ -49,38 +57,43 @@ export const serve_data = {
|
|||
return res.status(500).send(err.message);
|
||||
}
|
||||
} else {
|
||||
return res.status(500).send(err.message);
|
||||
}
|
||||
} else {
|
||||
if (data == null) {
|
||||
return res.status(404).send('Not found');
|
||||
} else {
|
||||
if (tileJSONFormat === 'pbf') {
|
||||
isGzipped = data.slice(0, 2).indexOf(
|
||||
Buffer.from([0x1f, 0x8b])) === 0;
|
||||
if (options.dataDecoratorFunc) {
|
||||
if (data == null) {
|
||||
return res.status(404).send('Not found');
|
||||
} else {
|
||||
if (tileJSONFormat === 'pbf') {
|
||||
isGzipped =
|
||||
data.slice(0, 2).indexOf(Buffer.from([0x1f, 0x8b])) === 0;
|
||||
if (options.dataDecoratorFunc) {
|
||||
if (isGzipped) {
|
||||
data = zlib.unzipSync(data);
|
||||
isGzipped = false;
|
||||
}
|
||||
data = options.dataDecoratorFunc(id, 'data', data, z, x, y);
|
||||
}
|
||||
}
|
||||
if (format === 'pbf') {
|
||||
headers['Content-Type'] = 'application/x-protobuf';
|
||||
} else if (format === 'geojson') {
|
||||
headers['Content-Type'] = 'application/json';
|
||||
|
||||
if (isGzipped) {
|
||||
data = zlib.unzipSync(data);
|
||||
isGzipped = false;
|
||||
}
|
||||
|
||||
if (isGzipped) {
|
||||
data = zlib.unzipSync(data);
|
||||
isGzipped = false;
|
||||
}
|
||||
|
||||
const tile = new VectorTile(new Pbf(data));
|
||||
const geojson = {
|
||||
'type': 'FeatureCollection',
|
||||
'features': []
|
||||
};
|
||||
for (const layerName in tile.layers) {
|
||||
const layer = tile.layers[layerName];
|
||||
for (let i = 0; i < layer.length; i++) {
|
||||
const feature = layer.feature(i);
|
||||
const featureGeoJSON = feature.toGeoJSON(x, y, z);
|
||||
featureGeoJSON.properties.layer = layerName;
|
||||
geojson.features.push(featureGeoJSON);
|
||||
const tile = new VectorTile(new Pbf(data));
|
||||
const geojson = {
|
||||
type: 'FeatureCollection',
|
||||
features: [],
|
||||
};
|
||||
for (const layerName in tile.layers) {
|
||||
const layer = tile.layers[layerName];
|
||||
for (let i = 0; i < layer.length; i++) {
|
||||
const feature = layer.feature(i);
|
||||
const featureGeoJSON = feature.toGeoJSON(x, y, z);
|
||||
featureGeoJSON.properties.layer = layerName;
|
||||
geojson.features.push(featureGeoJSON);
|
||||
}
|
||||
}
|
||||
data = JSON.stringify(geojson);
|
||||
}
|
||||
|
|
@ -106,10 +119,16 @@ export const serve_data = {
|
|||
return res.sendStatus(404);
|
||||
}
|
||||
const info = clone(item.tileJSON);
|
||||
info.tiles = getTileUrls(req, info.tiles,
|
||||
`data/${req.params.id}`, info.format, item.publicUrl, {
|
||||
'pbf': options.pbfAlias
|
||||
});
|
||||
info.tiles = getTileUrls(
|
||||
req,
|
||||
info.tiles,
|
||||
`data/${req.params.id}`,
|
||||
info.format,
|
||||
item.publicUrl,
|
||||
{
|
||||
pbf: options.pbfAlias,
|
||||
},
|
||||
);
|
||||
return res.send(info);
|
||||
});
|
||||
|
||||
|
|
@ -162,7 +181,7 @@ export const serve_data = {
|
|||
repo[id] = {
|
||||
tileJSON,
|
||||
publicUrl,
|
||||
source
|
||||
source,
|
||||
};
|
||||
});
|
||||
},
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import express from 'express';
|
|||
import fs from 'node:fs';
|
||||
import path from 'path';
|
||||
|
||||
import {getFontsPbf} from './utils.js';
|
||||
import { getFontsPbf } from './utils.js';
|
||||
|
||||
export const serve_font = (options, allowedFonts) => {
|
||||
const app = express().disable('x-powered-by');
|
||||
|
|
@ -26,8 +26,10 @@ export const serve_font = (options, allowedFonts) => {
|
|||
reject(err);
|
||||
return;
|
||||
}
|
||||
if (stats.isDirectory() &&
|
||||
fs.existsSync(path.join(fontPath, file, '0-255.pbf'))) {
|
||||
if (
|
||||
stats.isDirectory() &&
|
||||
fs.existsSync(path.join(fontPath, file, '0-255.pbf'))
|
||||
) {
|
||||
existingFonts[path.basename(file)] = true;
|
||||
}
|
||||
});
|
||||
|
|
@ -40,19 +42,26 @@ export const serve_font = (options, allowedFonts) => {
|
|||
const fontstack = decodeURI(req.params.fontstack);
|
||||
const range = req.params.range;
|
||||
|
||||
getFontsPbf(options.serveAllFonts ? null : allowedFonts,
|
||||
fontPath, fontstack, range, existingFonts).then((concated) => {
|
||||
res.header('Content-type', 'application/x-protobuf');
|
||||
res.header('Last-Modified', lastModified);
|
||||
return res.send(concated);
|
||||
}, (err) => res.status(400).send(err)
|
||||
getFontsPbf(
|
||||
options.serveAllFonts ? null : allowedFonts,
|
||||
fontPath,
|
||||
fontstack,
|
||||
range,
|
||||
existingFonts,
|
||||
).then(
|
||||
(concated) => {
|
||||
res.header('Content-type', 'application/x-protobuf');
|
||||
res.header('Last-Modified', lastModified);
|
||||
return res.send(concated);
|
||||
},
|
||||
(err) => res.status(400).send(err),
|
||||
);
|
||||
});
|
||||
|
||||
app.get('/fonts.json', (req, res, next) => {
|
||||
res.header('Content-type', 'application/json');
|
||||
return res.send(
|
||||
Object.keys(options.serveAllFonts ? existingFonts : allowedFonts).sort()
|
||||
Object.keys(options.serveAllFonts ? existingFonts : allowedFonts).sort(),
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,7 @@
|
|||
'use strict';
|
||||
|
||||
export const serve_rendered = {
|
||||
init: (options, repo) => {
|
||||
},
|
||||
add: (options, repo, params, id, publicUrl, dataResolver) => {
|
||||
},
|
||||
remove: (repo, id) => {
|
||||
}
|
||||
init: (options, repo) => {},
|
||||
add: (options, repo, params, id, publicUrl, dataResolver) => {},
|
||||
remove: (repo, id) => {},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -16,12 +16,12 @@ import mlgl from '@maplibre/maplibre-gl-native';
|
|||
import MBTiles from '@mapbox/mbtiles';
|
||||
import proj4 from 'proj4';
|
||||
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 {createCanvas} = pkg;
|
||||
const { createCanvas } = pkg;
|
||||
const mercator = new SphericalMercator();
|
||||
const getScale = (scale) => (scale || '@1x').slice(1, 2) | 0;
|
||||
|
||||
|
|
@ -38,7 +38,7 @@ const extensionToFormat = {
|
|||
'.jpg': 'jpeg',
|
||||
'.jpeg': 'jpeg',
|
||||
'.png': 'png',
|
||||
'.webp': 'webp'
|
||||
'.webp': 'webp',
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -46,18 +46,19 @@ const extensionToFormat = {
|
|||
* string is for unknown or unsupported formats.
|
||||
*/
|
||||
const cachedEmptyResponses = {
|
||||
'': Buffer.alloc(0)
|
||||
'': Buffer.alloc(0),
|
||||
};
|
||||
|
||||
/**
|
||||
* Create an appropriate mlgl response for http errors.
|
||||
*
|
||||
* @param {string} format The format (a sharp format or 'pbf').
|
||||
* @param {string} color The background color (or empty string for transparent).
|
||||
* @param {Function} callback The mlgl callback.
|
||||
*/
|
||||
function createEmptyResponse(format, color, callback) {
|
||||
if (!format || format === 'pbf') {
|
||||
callback(null, {data: cachedEmptyResponses['']});
|
||||
callback(null, { data: cachedEmptyResponses[''] });
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -71,7 +72,7 @@ function createEmptyResponse(format, color, callback) {
|
|||
const cacheKey = `${format},${color}`;
|
||||
const data = cachedEmptyResponses[cacheKey];
|
||||
if (data) {
|
||||
callback(null, {data: data});
|
||||
callback(null, { data: data });
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -83,14 +84,16 @@ function createEmptyResponse(format, color, callback) {
|
|||
raw: {
|
||||
width: 1,
|
||||
height: 1,
|
||||
channels: channels
|
||||
}
|
||||
}).toFormat(format).toBuffer((err, buffer, info) => {
|
||||
if (!err) {
|
||||
cachedEmptyResponses[cacheKey] = buffer;
|
||||
}
|
||||
callback(null, {data: buffer});
|
||||
});
|
||||
channels: channels,
|
||||
},
|
||||
})
|
||||
.toFormat(format)
|
||||
.toBuffer((err, buffer, info) => {
|
||||
if (!err) {
|
||||
cachedEmptyResponses[cacheKey] = buffer;
|
||||
}
|
||||
callback(null, { data: buffer });
|
||||
});
|
||||
}
|
||||
|
||||
const extractPathFromQuery = (query, transformer) => {
|
||||
|
|
@ -101,9 +104,9 @@ const extractPathFromQuery = (query, transformer) => {
|
|||
if (pairParts.length === 2) {
|
||||
let pair;
|
||||
if (query.latlng === '1' || query.latlng === 'true') {
|
||||
pair = [+(pairParts[1]), +(pairParts[0])];
|
||||
pair = [+pairParts[1], +pairParts[0]];
|
||||
} else {
|
||||
pair = [+(pairParts[0]), +(pairParts[1])];
|
||||
pair = [+pairParts[0], +pairParts[1]];
|
||||
}
|
||||
if (transformer) {
|
||||
pair = transformer(pair);
|
||||
|
|
@ -114,8 +117,7 @@ const extractPathFromQuery = (query, transformer) => {
|
|||
return path;
|
||||
};
|
||||
|
||||
const renderOverlay = (z, x, y, bearing, pitch, w, h, scale,
|
||||
path, query) => {
|
||||
const renderOverlay = (z, x, y, bearing, pitch, w, h, scale, path, query) => {
|
||||
if (!path || path.length < 2) {
|
||||
return null;
|
||||
}
|
||||
|
|
@ -131,7 +133,7 @@ const renderOverlay = (z, x, y, bearing, pitch, w, h, scale,
|
|||
const maxEdge = center[1] + h / 2;
|
||||
const minEdge = center[1] - h / 2;
|
||||
if (maxEdge > mapHeight) {
|
||||
center[1] -= (maxEdge - mapHeight);
|
||||
center[1] -= maxEdge - mapHeight;
|
||||
} else if (minEdge < 0) {
|
||||
center[1] -= minEdge;
|
||||
}
|
||||
|
|
@ -141,14 +143,13 @@ const renderOverlay = (z, x, y, bearing, pitch, w, h, scale,
|
|||
ctx.scale(scale, scale);
|
||||
if (bearing) {
|
||||
ctx.translate(w / 2, h / 2);
|
||||
ctx.rotate(-bearing / 180 * Math.PI);
|
||||
ctx.rotate((-bearing / 180) * Math.PI);
|
||||
ctx.translate(-center[0], -center[1]);
|
||||
} else {
|
||||
// optimized path
|
||||
ctx.translate(-center[0] + w / 2, -center[1] + h / 2);
|
||||
}
|
||||
const lineWidth = query.width !== undefined ?
|
||||
parseFloat(query.width) : 1;
|
||||
const lineWidth = query.width !== undefined ? parseFloat(query.width) : 1;
|
||||
ctx.lineWidth = lineWidth;
|
||||
ctx.strokeStyle = query.stroke || 'rgba(0,64,255,0.7)';
|
||||
ctx.fillStyle = query.fill || 'rgba(255,255,255,0.4)';
|
||||
|
|
@ -157,8 +158,10 @@ const renderOverlay = (z, x, y, bearing, pitch, w, h, scale,
|
|||
const px = precisePx(pair, z);
|
||||
ctx.lineTo(px[0], px[1]);
|
||||
}
|
||||
if (path[0][0] === path[path.length - 1][0] &&
|
||||
path[0][1] === path[path.length - 1][1]) {
|
||||
if (
|
||||
path[0][0] === path[path.length - 1][0] &&
|
||||
path[0][1] === path[path.length - 1][1]
|
||||
) {
|
||||
ctx.closePath();
|
||||
}
|
||||
ctx.fill();
|
||||
|
|
@ -172,18 +175,18 @@ const renderOverlay = (z, x, y, bearing, pitch, w, h, scale,
|
|||
const calcZForBBox = (bbox, w, h, query) => {
|
||||
let z = 25;
|
||||
|
||||
const padding = query.padding !== undefined ?
|
||||
parseFloat(query.padding) : 0.1;
|
||||
const padding = query.padding !== undefined ? parseFloat(query.padding) : 0.1;
|
||||
|
||||
const minCorner = mercator.px([bbox[0], bbox[3]], z);
|
||||
const maxCorner = mercator.px([bbox[2], bbox[1]], z);
|
||||
const w_ = w / (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[1] - minCorner[1]) / h_)
|
||||
) / Math.LN2;
|
||||
Math.log((maxCorner[1] - minCorner[1]) / h_),
|
||||
) / Math.LN2;
|
||||
|
||||
z = Math.max(Math.log(Math.max(w, h) / 256) / Math.LN2, Math.min(25, z));
|
||||
|
||||
|
|
@ -225,14 +228,36 @@ export const serve_rendered = {
|
|||
|
||||
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') => {
|
||||
if (Math.abs(lon) > 180 || Math.abs(lat) > 85.06 ||
|
||||
lon !== lon || lat !== lat) {
|
||||
const respondImage = (
|
||||
item,
|
||||
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');
|
||||
}
|
||||
if (Math.min(width, height) <= 0 ||
|
||||
if (
|
||||
Math.min(width, height) <= 0 ||
|
||||
Math.max(width, height) * scale > (options.maxSize || 2048) ||
|
||||
width !== width || height !== height) {
|
||||
width !== width ||
|
||||
height !== height
|
||||
) {
|
||||
return res.status(400).send('Invalid size');
|
||||
}
|
||||
if (format === 'png' || format === 'webp') {
|
||||
|
|
@ -256,7 +281,7 @@ export const serve_rendered = {
|
|||
bearing: bearing,
|
||||
pitch: pitch,
|
||||
width: width,
|
||||
height: height
|
||||
height: height,
|
||||
};
|
||||
if (z === 0) {
|
||||
params.width *= 2;
|
||||
|
|
@ -296,18 +321,21 @@ export const serve_rendered = {
|
|||
raw: {
|
||||
width: params.width * scale,
|
||||
height: params.height * scale,
|
||||
channels: 4
|
||||
}
|
||||
channels: 4,
|
||||
},
|
||||
});
|
||||
|
||||
if (z > 2 && tileMargin > 0) {
|
||||
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({
|
||||
left: tileMargin * scale,
|
||||
top: (tileMargin + yoffset) * scale,
|
||||
width: width * scale,
|
||||
height: height * scale
|
||||
height: height * scale,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -317,7 +345,7 @@ export const serve_rendered = {
|
|||
}
|
||||
|
||||
if (opt_overlay) {
|
||||
image.composite([{input: opt_overlay}]);
|
||||
image.composite([{ input: opt_overlay }]);
|
||||
}
|
||||
if (item.watermark) {
|
||||
const canvas = createCanvas(scale * width, scale * height);
|
||||
|
|
@ -330,17 +358,17 @@ export const serve_rendered = {
|
|||
ctx.fillStyle = 'rgba(0,0,0,.4)';
|
||||
ctx.fillText(item.watermark, 5, height - 5);
|
||||
|
||||
image.composite([{input: canvas.toBuffer()}]);
|
||||
image.composite([{ input: canvas.toBuffer() }]);
|
||||
}
|
||||
|
||||
const formatQuality = (options.formatQuality || {})[format];
|
||||
|
||||
if (format === 'png') {
|
||||
image.png({adaptiveFiltering: false});
|
||||
image.png({ adaptiveFiltering: false });
|
||||
} else if (format === 'jpeg') {
|
||||
image.jpeg({quality: formatQuality || 80});
|
||||
image.jpeg({ quality: formatQuality || 80 });
|
||||
} else if (format === 'webp') {
|
||||
image.webp({quality: formatQuality || 90});
|
||||
image.webp({ quality: formatQuality || 90 });
|
||||
}
|
||||
image.toBuffer((err, buffer, info) => {
|
||||
if (!buffer) {
|
||||
|
|
@ -349,7 +377,7 @@ export const serve_rendered = {
|
|||
|
||||
res.set({
|
||||
'Last-Modified': item.lastModified,
|
||||
'Content-Type': `image/${format}`
|
||||
'Content-Type': `image/${format}`,
|
||||
});
|
||||
return res.status(200).send(buffer);
|
||||
});
|
||||
|
|
@ -357,44 +385,73 @@ export const serve_rendered = {
|
|||
});
|
||||
};
|
||||
|
||||
app.get(`/:id/:z(\\d+)/:x(\\d+)/:y(\\d+):scale(${scalePattern})?.:format([\\w]+)`, (req, res, next) => {
|
||||
const item = repo[req.params.id];
|
||||
if (!item) {
|
||||
return res.sendStatus(404);
|
||||
}
|
||||
|
||||
const modifiedSince = req.get('if-modified-since'); const cc = req.get('cache-control');
|
||||
if (modifiedSince && (!cc || cc.indexOf('no-cache') === -1)) {
|
||||
if (new Date(item.lastModified) <= new Date(modifiedSince)) {
|
||||
return res.sendStatus(304);
|
||||
app.get(
|
||||
`/:id/:z(\\d+)/:x(\\d+)/:y(\\d+):scale(${scalePattern})?.:format([\\w]+)`,
|
||||
(req, res, next) => {
|
||||
const item = repo[req.params.id];
|
||||
if (!item) {
|
||||
return res.sendStatus(404);
|
||||
}
|
||||
}
|
||||
|
||||
const z = req.params.z | 0;
|
||||
const x = req.params.x | 0;
|
||||
const y = req.params.y | 0;
|
||||
const scale = getScale(req.params.scale);
|
||||
const format = req.params.format;
|
||||
if (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');
|
||||
}
|
||||
const tileSize = 256;
|
||||
const tileCenter = mercator.ll([
|
||||
((x + 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);
|
||||
});
|
||||
const modifiedSince = req.get('if-modified-since');
|
||||
const cc = req.get('cache-control');
|
||||
if (modifiedSince && (!cc || cc.indexOf('no-cache') === -1)) {
|
||||
if (new Date(item.lastModified) <= new Date(modifiedSince)) {
|
||||
return res.sendStatus(304);
|
||||
}
|
||||
}
|
||||
|
||||
const z = req.params.z | 0;
|
||||
const x = req.params.x | 0;
|
||||
const y = req.params.y | 0;
|
||||
const scale = getScale(req.params.scale);
|
||||
const format = req.params.format;
|
||||
if (
|
||||
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');
|
||||
}
|
||||
const tileSize = 256;
|
||||
const tileCenter = mercator.ll(
|
||||
[
|
||||
((x + 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,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
if (options.serveStaticMaps !== false) {
|
||||
const staticPattern =
|
||||
`/:id/static/:raw(raw)?/%s/:width(\\d+)x:height(\\d+):scale(${scalePattern})?.:format([\\w]+)`;
|
||||
const staticPattern = `/:id/static/:raw(raw)?/%s/:width(\\d+)x:height(\\d+):scale(${scalePattern})?.:format([\\w]+)`;
|
||||
|
||||
const centerPattern =
|
||||
util.format(':x(%s),:y(%s),:z(%s)(@:bearing(%s)(,:pitch(%s))?)?',
|
||||
FLOAT_PATTERN, FLOAT_PATTERN, FLOAT_PATTERN,
|
||||
FLOAT_PATTERN, FLOAT_PATTERN);
|
||||
const centerPattern = util.format(
|
||||
':x(%s),:y(%s),:z(%s)(@:bearing(%s)(,:pitch(%s))?)?',
|
||||
FLOAT_PATTERN,
|
||||
FLOAT_PATTERN,
|
||||
FLOAT_PATTERN,
|
||||
FLOAT_PATTERN,
|
||||
FLOAT_PATTERN,
|
||||
);
|
||||
|
||||
app.get(util.format(staticPattern, centerPattern), (req, res, next) => {
|
||||
const item = repo[req.params.id];
|
||||
|
|
@ -416,8 +473,9 @@ export const serve_rendered = {
|
|||
return res.status(404).send('Invalid zoom');
|
||||
}
|
||||
|
||||
const transformer = raw ?
|
||||
mercator.inverse.bind(mercator) : item.dataProjWGStoInternalWGS;
|
||||
const transformer = raw
|
||||
? mercator.inverse.bind(mercator)
|
||||
: item.dataProjWGStoInternalWGS;
|
||||
|
||||
if (transformer) {
|
||||
const ll = transformer([x, y]);
|
||||
|
|
@ -426,9 +484,35 @@ export const serve_rendered = {
|
|||
}
|
||||
|
||||
const path = extractPathFromQuery(req.query, transformer);
|
||||
const overlay = renderOverlay(z, x, y, bearing, pitch, w, h, scale, path, req.query);
|
||||
const overlay = renderOverlay(
|
||||
z,
|
||||
x,
|
||||
y,
|
||||
bearing,
|
||||
pitch,
|
||||
w,
|
||||
h,
|
||||
scale,
|
||||
path,
|
||||
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 = (req, res, next) => {
|
||||
|
|
@ -437,11 +521,17 @@ export const serve_rendered = {
|
|||
return res.sendStatus(404);
|
||||
}
|
||||
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];
|
||||
|
||||
const transformer = raw ?
|
||||
mercator.inverse.bind(mercator) : item.dataProjWGStoInternalWGS;
|
||||
const transformer = raw
|
||||
? mercator.inverse.bind(mercator)
|
||||
: item.dataProjWGStoInternalWGS;
|
||||
|
||||
if (transformer) {
|
||||
const minCorner = transformer(bbox.slice(0, 2));
|
||||
|
|
@ -465,14 +555,44 @@ export const serve_rendered = {
|
|||
const pitch = 0;
|
||||
|
||||
const path = extractPathFromQuery(req.query, transformer);
|
||||
const overlay = renderOverlay(z, x, y, bearing, pitch, w, h, scale, path, req.query);
|
||||
const overlay = renderOverlay(
|
||||
z,
|
||||
x,
|
||||
y,
|
||||
bearing,
|
||||
pitch,
|
||||
w,
|
||||
h,
|
||||
scale,
|
||||
path,
|
||||
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 boundsPattern =
|
||||
util.format(':minx(%s),:miny(%s),:maxx(%s),:maxy(%s)',
|
||||
FLOAT_PATTERN, FLOAT_PATTERN, FLOAT_PATTERN, FLOAT_PATTERN);
|
||||
const boundsPattern = util.format(
|
||||
':minx(%s),:miny(%s),:maxx(%s),:maxy(%s)',
|
||||
FLOAT_PATTERN,
|
||||
FLOAT_PATTERN,
|
||||
FLOAT_PATTERN,
|
||||
FLOAT_PATTERN,
|
||||
);
|
||||
|
||||
app.get(util.format(staticPattern, boundsPattern), serveBounds);
|
||||
|
||||
|
|
@ -513,8 +633,9 @@ export const serve_rendered = {
|
|||
const scale = getScale(req.params.scale);
|
||||
const format = req.params.format;
|
||||
|
||||
const transformer = raw ?
|
||||
mercator.inverse.bind(mercator) : item.dataProjWGStoInternalWGS;
|
||||
const transformer = raw
|
||||
? mercator.inverse.bind(mercator)
|
||||
: item.dataProjWGStoInternalWGS;
|
||||
|
||||
const path = extractPathFromQuery(req.query, transformer);
|
||||
if (path.length < 2) {
|
||||
|
|
@ -530,17 +651,44 @@ export const serve_rendered = {
|
|||
}
|
||||
|
||||
const bbox_ = mercator.convert(bbox, '900913');
|
||||
const center = mercator.inverse(
|
||||
[(bbox_[0] + bbox_[2]) / 2, (bbox_[1] + bbox_[3]) / 2]
|
||||
);
|
||||
const center = mercator.inverse([
|
||||
(bbox_[0] + bbox_[2]) / 2,
|
||||
(bbox_[1] + bbox_[3]) / 2,
|
||||
]);
|
||||
|
||||
const z = calcZForBBox(bbox, w, h, req.query);
|
||||
const x = center[0];
|
||||
const y = center[1];
|
||||
|
||||
const overlay = renderOverlay(z, x, y, bearing, pitch, w, h, scale, path, req.query);
|
||||
const overlay = renderOverlay(
|
||||
z,
|
||||
x,
|
||||
y,
|
||||
bearing,
|
||||
pitch,
|
||||
w,
|
||||
h,
|
||||
scale,
|
||||
path,
|
||||
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',
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -550,8 +698,13 @@ export const serve_rendered = {
|
|||
return res.sendStatus(404);
|
||||
}
|
||||
const info = clone(item.tileJSON);
|
||||
info.tiles = getTileUrls(req, info.tiles,
|
||||
`styles/${req.params.id}`, info.format, item.publicUrl);
|
||||
info.tiles = getTileUrls(
|
||||
req,
|
||||
info.tiles,
|
||||
`styles/${req.params.id}`,
|
||||
info.format,
|
||||
item.publicUrl,
|
||||
);
|
||||
return res.send(info);
|
||||
});
|
||||
|
||||
|
|
@ -561,7 +714,7 @@ export const serve_rendered = {
|
|||
const map = {
|
||||
renderers: [],
|
||||
renderers_static: [],
|
||||
sources: {}
|
||||
sources: {},
|
||||
};
|
||||
|
||||
let styleJSON;
|
||||
|
|
@ -577,19 +730,26 @@ export const serve_rendered = {
|
|||
const dir = options.paths[protocol];
|
||||
const file = unescape(req.url).substring(protocol.length + 3);
|
||||
fs.readFile(path.join(dir, file), (err, data) => {
|
||||
callback(err, {data: data});
|
||||
callback(err, { data: data });
|
||||
});
|
||||
} else if (protocol === 'fonts') {
|
||||
const parts = req.url.split('/');
|
||||
const fontstack = unescape(parts[2]);
|
||||
const range = parts[3].split('.')[0];
|
||||
getFontsPbf(
|
||||
null, options.paths[protocol], fontstack, range, existingFonts
|
||||
).then((concated) => {
|
||||
callback(null, {data: concated});
|
||||
}, (err) => {
|
||||
callback(err, {data: null});
|
||||
});
|
||||
null,
|
||||
options.paths[protocol],
|
||||
fontstack,
|
||||
range,
|
||||
existingFonts,
|
||||
).then(
|
||||
(concated) => {
|
||||
callback(null, { data: concated });
|
||||
},
|
||||
(err) => {
|
||||
callback(err, { data: null });
|
||||
},
|
||||
);
|
||||
} else if (protocol === 'mbtiles') {
|
||||
const parts = req.url.split('/');
|
||||
const sourceId = parts[2];
|
||||
|
|
@ -601,8 +761,13 @@ export const serve_rendered = {
|
|||
const format = parts[5].split('.')[1];
|
||||
source.getTile(z, x, y, (err, data, headers) => {
|
||||
if (err) {
|
||||
if (options.verbose) console.log('MBTiles error, serving empty', err);
|
||||
createEmptyResponse(sourceInfo.format, sourceInfo.color, callback);
|
||||
if (options.verbose)
|
||||
console.log('MBTiles error, serving empty', err);
|
||||
createEmptyResponse(
|
||||
sourceInfo.format,
|
||||
sourceInfo.color,
|
||||
callback,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -615,11 +780,23 @@ export const serve_rendered = {
|
|||
try {
|
||||
response.data = zlib.unzipSync(data);
|
||||
} 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) {
|
||||
response.data = options.dataDecoratorFunc(
|
||||
sourceId, 'data', response.data, z, x, y);
|
||||
sourceId,
|
||||
'data',
|
||||
response.data,
|
||||
z,
|
||||
x,
|
||||
y,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
response.data = data;
|
||||
|
|
@ -628,36 +805,39 @@ export const serve_rendered = {
|
|||
callback(null, response);
|
||||
});
|
||||
} else if (protocol === 'http' || protocol === 'https') {
|
||||
request({
|
||||
url: req.url,
|
||||
encoding: null,
|
||||
gzip: true
|
||||
}, (err, res, body) => {
|
||||
const parts = url.parse(req.url);
|
||||
const extension = path.extname(parts.pathname).toLowerCase();
|
||||
const format = extensionToFormat[extension] || '';
|
||||
if (err || res.statusCode < 200 || res.statusCode >= 300) {
|
||||
// console.log('HTTP error', err || res.statusCode);
|
||||
createEmptyResponse(format, '', callback);
|
||||
return;
|
||||
}
|
||||
request(
|
||||
{
|
||||
url: req.url,
|
||||
encoding: null,
|
||||
gzip: true,
|
||||
},
|
||||
(err, res, body) => {
|
||||
const parts = url.parse(req.url);
|
||||
const extension = path.extname(parts.pathname).toLowerCase();
|
||||
const format = extensionToFormat[extension] || '';
|
||||
if (err || res.statusCode < 200 || res.statusCode >= 300) {
|
||||
// console.log('HTTP error', err || res.statusCode);
|
||||
createEmptyResponse(format, '', callback);
|
||||
return;
|
||||
}
|
||||
|
||||
const response = {};
|
||||
if (res.headers.modified) {
|
||||
response.modified = new Date(res.headers.modified);
|
||||
}
|
||||
if (res.headers.expires) {
|
||||
response.expires = new Date(res.headers.expires);
|
||||
}
|
||||
if (res.headers.etag) {
|
||||
response.etag = res.headers.etag;
|
||||
}
|
||||
const response = {};
|
||||
if (res.headers.modified) {
|
||||
response.modified = new Date(res.headers.modified);
|
||||
}
|
||||
if (res.headers.expires) {
|
||||
response.expires = new Date(res.headers.expires);
|
||||
}
|
||||
if (res.headers.etag) {
|
||||
response.etag = res.headers.etag;
|
||||
}
|
||||
|
||||
response.data = body;
|
||||
callback(null, response);
|
||||
});
|
||||
response.data = body;
|
||||
callback(null, response);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
renderer.load(styleJSON);
|
||||
createCallback(null, renderer);
|
||||
|
|
@ -668,7 +848,7 @@ export const serve_rendered = {
|
|||
create: createRenderer.bind(null, ratio),
|
||||
destroy: (renderer) => {
|
||||
renderer.release();
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
|
|
@ -682,16 +862,20 @@ export const serve_rendered = {
|
|||
}
|
||||
|
||||
if (styleJSON.sprite && !httpTester.test(styleJSON.sprite)) {
|
||||
styleJSON.sprite = 'sprites://' +
|
||||
styleJSON.sprite =
|
||||
'sprites://' +
|
||||
styleJSON.sprite
|
||||
.replace('{style}', path.basename(styleFile, '.json'))
|
||||
.replace('{styleJsonFolder}', path.relative(options.paths.sprites, path.dirname(styleJSONPath)));
|
||||
.replace('{style}', path.basename(styleFile, '.json'))
|
||||
.replace(
|
||||
'{styleJsonFolder}',
|
||||
path.relative(options.paths.sprites, path.dirname(styleJSONPath)),
|
||||
);
|
||||
}
|
||||
if (styleJSON.glyphs && !httpTester.test(styleJSON.glyphs)) {
|
||||
styleJSON.glyphs = `fonts://${styleJSON.glyphs}`;
|
||||
}
|
||||
|
||||
for (const layer of (styleJSON.layers || [])) {
|
||||
for (const layer of styleJSON.layers || []) {
|
||||
if (layer && layer.paint) {
|
||||
// Remove (flatten) 3D buildings
|
||||
if (layer.paint['fill-extrusion-height']) {
|
||||
|
|
@ -704,14 +888,14 @@ export const serve_rendered = {
|
|||
}
|
||||
|
||||
const tileJSON = {
|
||||
'tilejson': '2.0.0',
|
||||
'name': styleJSON.name,
|
||||
'attribution': '',
|
||||
'minzoom': 0,
|
||||
'maxzoom': 20,
|
||||
'bounds': [-180, -85.0511, 180, 85.0511],
|
||||
'format': 'png',
|
||||
'type': 'baselayer'
|
||||
tilejson: '2.0.0',
|
||||
name: styleJSON.name,
|
||||
attribution: '',
|
||||
minzoom: 0,
|
||||
maxzoom: 20,
|
||||
bounds: [-180, -85.0511, 180, 85.0511],
|
||||
format: 'png',
|
||||
type: 'baselayer',
|
||||
};
|
||||
const attributionOverride = params.tilejson && params.tilejson.attribution;
|
||||
Object.assign(tileJSON, params.tilejson || {});
|
||||
|
|
@ -724,7 +908,7 @@ export const serve_rendered = {
|
|||
map,
|
||||
dataProjWGStoInternalWGS: null,
|
||||
lastModified: new Date().toUTCString(),
|
||||
watermark: params.watermark || options.watermark
|
||||
watermark: params.watermark || options.watermark,
|
||||
};
|
||||
repo[id] = repoobj;
|
||||
|
||||
|
|
@ -738,8 +922,8 @@ export const serve_rendered = {
|
|||
delete source.url;
|
||||
|
||||
let mbtilesFile = url.substring('mbtiles://'.length);
|
||||
const fromData = mbtilesFile[0] === '{' &&
|
||||
mbtilesFile[mbtilesFile.length - 1] === '}';
|
||||
const fromData =
|
||||
mbtilesFile[0] === '{' && mbtilesFile[mbtilesFile.length - 1] === '}';
|
||||
|
||||
if (fromData) {
|
||||
mbtilesFile = mbtilesFile.substr(1, mbtilesFile.length - 2);
|
||||
|
|
@ -754,52 +938,58 @@ export const serve_rendered = {
|
|||
}
|
||||
}
|
||||
|
||||
queue.push(new Promise((resolve, reject) => {
|
||||
mbtilesFile = path.resolve(options.paths.mbtiles, mbtilesFile);
|
||||
const mbtilesFileStats = fs.statSync(mbtilesFile);
|
||||
if (!mbtilesFileStats.isFile() || mbtilesFileStats.size === 0) {
|
||||
throw Error(`Not valid MBTiles file: ${mbtilesFile}`);
|
||||
}
|
||||
map.sources[name] = new MBTiles(mbtilesFile + '?mode=ro', err => {
|
||||
map.sources[name].getInfo((err, info) => {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!repoobj.dataProjWGStoInternalWGS && info.proj4) {
|
||||
// how to do this for multiple sources with different proj4 defs?
|
||||
const to3857 = proj4('EPSG:3857');
|
||||
const toDataProj = proj4(info.proj4);
|
||||
repoobj.dataProjWGStoInternalWGS = (xy) => to3857.inverse(toDataProj.forward(xy));
|
||||
}
|
||||
|
||||
const type = source.type;
|
||||
Object.assign(source, info);
|
||||
source.type = type;
|
||||
source.tiles = [
|
||||
// meta url which will be detected when requested
|
||||
`mbtiles://${name}/{z}/{x}/{y}.${info.format || 'pbf'}`
|
||||
];
|
||||
delete source.scheme;
|
||||
|
||||
if (options.dataDecoratorFunc) {
|
||||
source = options.dataDecoratorFunc(name, 'tilejson', source);
|
||||
}
|
||||
|
||||
if (!attributionOverride &&
|
||||
source.attribution && source.attribution.length > 0) {
|
||||
if (!tileJSON.attribution.includes(source.attribution)) {
|
||||
if (tileJSON.attribution.length > 0) {
|
||||
tileJSON.attribution += ' | ';
|
||||
}
|
||||
tileJSON.attribution += source.attribution;
|
||||
queue.push(
|
||||
new Promise((resolve, reject) => {
|
||||
mbtilesFile = path.resolve(options.paths.mbtiles, mbtilesFile);
|
||||
const mbtilesFileStats = fs.statSync(mbtilesFile);
|
||||
if (!mbtilesFileStats.isFile() || mbtilesFileStats.size === 0) {
|
||||
throw Error(`Not valid MBTiles file: ${mbtilesFile}`);
|
||||
}
|
||||
map.sources[name] = new MBTiles(mbtilesFile + '?mode=ro', (err) => {
|
||||
map.sources[name].getInfo((err, info) => {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
resolve();
|
||||
|
||||
if (!repoobj.dataProjWGStoInternalWGS && info.proj4) {
|
||||
// how to do this for multiple sources with different proj4 defs?
|
||||
const to3857 = proj4('EPSG:3857');
|
||||
const toDataProj = proj4(info.proj4);
|
||||
repoobj.dataProjWGStoInternalWGS = (xy) =>
|
||||
to3857.inverse(toDataProj.forward(xy));
|
||||
}
|
||||
|
||||
const type = source.type;
|
||||
Object.assign(source, info);
|
||||
source.type = type;
|
||||
source.tiles = [
|
||||
// meta url which will be detected when requested
|
||||
`mbtiles://${name}/{z}/{x}/{y}.${info.format || 'pbf'}`,
|
||||
];
|
||||
delete source.scheme;
|
||||
|
||||
if (options.dataDecoratorFunc) {
|
||||
source = options.dataDecoratorFunc(name, 'tilejson', source);
|
||||
}
|
||||
|
||||
if (
|
||||
!attributionOverride &&
|
||||
source.attribution &&
|
||||
source.attribution.length > 0
|
||||
) {
|
||||
if (!tileJSON.attribution.includes(source.attribution)) {
|
||||
if (tileJSON.attribution.length > 0) {
|
||||
tileJSON.attribution += ' | ';
|
||||
}
|
||||
tileJSON.attribution += source.attribution;
|
||||
}
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
}));
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -813,7 +1003,12 @@ export const serve_rendered = {
|
|||
const minPoolSize = minPoolSizes[i];
|
||||
const maxPoolSize = Math.max(minPoolSize, maxPoolSizes[j]);
|
||||
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,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -830,5 +1025,5 @@ export const serve_rendered = {
|
|||
});
|
||||
}
|
||||
delete repo[id];
|
||||
}
|
||||
};
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -5,14 +5,14 @@ import fs from 'node:fs';
|
|||
|
||||
import clone from 'clone';
|
||||
import express from 'express';
|
||||
import {validate} from '@maplibre/maplibre-gl-style-spec';
|
||||
import { validate } from '@maplibre/maplibre-gl-style-spec';
|
||||
|
||||
import {getPublicUrl} from './utils.js';
|
||||
import { getPublicUrl } from './utils.js';
|
||||
|
||||
const httpTester = /^(http(s)?:)?\/\//;
|
||||
|
||||
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;
|
||||
}
|
||||
const queryParams = [];
|
||||
|
|
@ -23,8 +23,7 @@ const fixUrl = (req, url, publicUrl, opt_nokey) => {
|
|||
if (queryParams.length) {
|
||||
query = `?${queryParams.join('&')}`;
|
||||
}
|
||||
return url.replace(
|
||||
'local://', getPublicUrl(publicUrl, req)) + query;
|
||||
return url.replace('local://', getPublicUrl(publicUrl, req)) + query;
|
||||
};
|
||||
|
||||
export const serve_style = {
|
||||
|
|
@ -43,10 +42,20 @@ export const serve_style = {
|
|||
}
|
||||
// mapbox-gl-js viewer cannot handle sprite urls with query
|
||||
if (styleJSON_.sprite) {
|
||||
styleJSON_.sprite = fixUrl(req, styleJSON_.sprite, item.publicUrl, false);
|
||||
styleJSON_.sprite = fixUrl(
|
||||
req,
|
||||
styleJSON_.sprite,
|
||||
item.publicUrl,
|
||||
false,
|
||||
);
|
||||
}
|
||||
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_);
|
||||
});
|
||||
|
|
@ -89,7 +98,9 @@ export const serve_style = {
|
|||
|
||||
const validationErrors = validate(styleFileData);
|
||||
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) {
|
||||
console.log(`${err.line}: ${err.message}`);
|
||||
}
|
||||
|
|
@ -102,8 +113,8 @@ export const serve_style = {
|
|||
const url = source.url;
|
||||
if (url && url.lastIndexOf('mbtiles:', 0) === 0) {
|
||||
let mbtilesFile = url.substring('mbtiles://'.length);
|
||||
const fromData = mbtilesFile[0] === '{' &&
|
||||
mbtilesFile[mbtilesFile.length - 1] === '}';
|
||||
const fromData =
|
||||
mbtilesFile[0] === '{' && mbtilesFile[mbtilesFile.length - 1] === '}';
|
||||
|
||||
if (fromData) {
|
||||
mbtilesFile = mbtilesFile.substr(1, mbtilesFile.length - 2);
|
||||
|
|
@ -135,10 +146,14 @@ export const serve_style = {
|
|||
let spritePath;
|
||||
|
||||
if (styleJSON.sprite && !httpTester.test(styleJSON.sprite)) {
|
||||
spritePath = path.join(options.paths.sprites,
|
||||
styleJSON.sprite
|
||||
.replace('{style}', path.basename(styleFile, '.json'))
|
||||
.replace('{styleJsonFolder}', path.relative(options.paths.sprites, path.dirname(styleFile)))
|
||||
spritePath = path.join(
|
||||
options.paths.sprites,
|
||||
styleJSON.sprite
|
||||
.replace('{style}', path.basename(styleFile, '.json'))
|
||||
.replace(
|
||||
'{styleJsonFolder}',
|
||||
path.relative(options.paths.sprites, path.dirname(styleFile)),
|
||||
),
|
||||
);
|
||||
styleJSON.sprite = `local://styles/${id}/sprite`;
|
||||
}
|
||||
|
|
@ -150,9 +165,9 @@ export const serve_style = {
|
|||
styleJSON,
|
||||
spritePath,
|
||||
publicUrl,
|
||||
name: styleJSON.name
|
||||
name: styleJSON.name,
|
||||
};
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
},
|
||||
};
|
||||
|
|
|
|||
356
src/server.js
356
src/server.js
|
|
@ -2,8 +2,7 @@
|
|||
'use strict';
|
||||
|
||||
import os from 'os';
|
||||
process.env.UV_THREADPOOL_SIZE =
|
||||
Math.ceil(Math.max(4, os.cpus().length * 1.5));
|
||||
process.env.UV_THREADPOOL_SIZE = Math.ceil(Math.max(4, os.cpus().length * 1.5));
|
||||
|
||||
import fs from 'node:fs';
|
||||
import path from 'path';
|
||||
|
|
@ -17,19 +16,27 @@ import handlebars from 'handlebars';
|
|||
import SphericalMercator from '@mapbox/sphericalmercator';
|
||||
const mercator = new SphericalMercator();
|
||||
import morgan from 'morgan';
|
||||
import {serve_data} from './serve_data.js';
|
||||
import {serve_style} from './serve_style.js';
|
||||
import {serve_font} from './serve_font.js';
|
||||
import {getTileUrls, getPublicUrl} from './utils.js';
|
||||
import { serve_data } from './serve_data.js';
|
||||
import { serve_style } from './serve_style.js';
|
||||
import { serve_font } from './serve_font.js';
|
||||
import { getTileUrls, getPublicUrl } from './utils.js';
|
||||
|
||||
import {fileURLToPath} from 'url';
|
||||
import { fileURLToPath } from 'url';
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
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 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
|
||||
*/
|
||||
export function server(opts) {
|
||||
console.log('Starting server');
|
||||
|
||||
|
|
@ -38,18 +45,24 @@ export function server(opts) {
|
|||
styles: {},
|
||||
rendered: {},
|
||||
data: {},
|
||||
fonts: {}
|
||||
fonts: {},
|
||||
};
|
||||
|
||||
app.enable('trust proxy');
|
||||
|
||||
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;
|
||||
app.use(morgan(logFormat, {
|
||||
stream: opts.logFile ? fs.createWriteStream(opts.logFile, {flags: 'a'}) : process.stdout,
|
||||
skip: (req, res) => opts.silent && (res.statusCode === 200 || res.statusCode === 304)
|
||||
}));
|
||||
app.use(
|
||||
morgan(logFormat, {
|
||||
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;
|
||||
|
|
@ -74,7 +87,8 @@ export function server(opts) {
|
|||
options.paths = paths;
|
||||
paths.root = path.resolve(
|
||||
configPath ? path.dirname(configPath) : process.cwd(),
|
||||
paths.root || '');
|
||||
paths.root || '',
|
||||
);
|
||||
paths.styles = path.resolve(paths.root, paths.styles || '');
|
||||
paths.fonts = path.resolve(paths.root, paths.fonts || '');
|
||||
paths.sprites = path.resolve(paths.root, paths.sprites || '');
|
||||
|
|
@ -84,7 +98,9 @@ export function server(opts) {
|
|||
|
||||
const checkPath = (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);
|
||||
}
|
||||
};
|
||||
|
|
@ -95,7 +111,10 @@ export function server(opts) {
|
|||
|
||||
if (options.dataDecorator) {
|
||||
try {
|
||||
options.dataDecoratorFunc = require(path.resolve(paths.root, options.dataDecorator));
|
||||
options.dataDecoratorFunc = require(path.resolve(
|
||||
paths.root,
|
||||
options.dataDecorator,
|
||||
));
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
|
|
@ -109,54 +128,69 @@ export function server(opts) {
|
|||
app.use('/styles/', serve_style.init(options, serving.styles));
|
||||
if (!isLight) {
|
||||
startupPromises.push(
|
||||
serve_rendered.init(options, serving.rendered)
|
||||
.then((sub) => {
|
||||
app.use('/styles/', sub);
|
||||
})
|
||||
serve_rendered.init(options, serving.rendered).then((sub) => {
|
||||
app.use('/styles/', sub);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
const addStyle = (id, item, allowMoreData, reportFonts) => {
|
||||
let success = true;
|
||||
if (item.serve_data !== false) {
|
||||
success = serve_style.add(options, serving.styles, item, id, opts.publicUrl,
|
||||
(mbtiles, fromData) => {
|
||||
let dataItemId;
|
||||
for (const id of Object.keys(data)) {
|
||||
if (fromData) {
|
||||
if (id === mbtiles) {
|
||||
dataItemId = id;
|
||||
}
|
||||
} else {
|
||||
if (data[id].mbtiles === mbtiles) {
|
||||
dataItemId = id;
|
||||
}
|
||||
success = serve_style.add(
|
||||
options,
|
||||
serving.styles,
|
||||
item,
|
||||
id,
|
||||
opts.publicUrl,
|
||||
(mbtiles, fromData) => {
|
||||
let dataItemId;
|
||||
for (const id of Object.keys(data)) {
|
||||
if (fromData) {
|
||||
if (id === mbtiles) {
|
||||
dataItemId = id;
|
||||
}
|
||||
}
|
||||
if (dataItemId) { // mbtiles exist in the data config
|
||||
return dataItemId;
|
||||
} else {
|
||||
if (fromData || !allowMoreData) {
|
||||
console.log(`ERROR: style "${item.style}" using unknown mbtiles "${mbtiles}"! Skipping...`);
|
||||
return undefined;
|
||||
} else {
|
||||
let id = mbtiles.substr(0, mbtiles.lastIndexOf('.')) || mbtiles;
|
||||
while (data[id]) id += '_';
|
||||
data[id] = {
|
||||
'mbtiles': mbtiles
|
||||
};
|
||||
return id;
|
||||
if (data[id].mbtiles === mbtiles) {
|
||||
dataItemId = id;
|
||||
}
|
||||
}
|
||||
}, (font) => {
|
||||
if (reportFonts) {
|
||||
serving.fonts[font] = true;
|
||||
}
|
||||
if (dataItemId) {
|
||||
// mbtiles exist in the data config
|
||||
return dataItemId;
|
||||
} else {
|
||||
if (fromData || !allowMoreData) {
|
||||
console.log(
|
||||
`ERROR: style "${item.style}" using unknown mbtiles "${mbtiles}"! Skipping...`,
|
||||
);
|
||||
return undefined;
|
||||
} else {
|
||||
let id = mbtiles.substr(0, mbtiles.lastIndexOf('.')) || mbtiles;
|
||||
while (data[id]) id += '_';
|
||||
data[id] = {
|
||||
mbtiles: mbtiles,
|
||||
};
|
||||
return id;
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
(font) => {
|
||||
if (reportFonts) {
|
||||
serving.fonts[font] = true;
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
if (success && item.serve_rendered !== false) {
|
||||
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) => {
|
||||
let mbtilesFile;
|
||||
for (const id of Object.keys(data)) {
|
||||
|
|
@ -165,8 +199,9 @@ export function server(opts) {
|
|||
}
|
||||
}
|
||||
return mbtilesFile;
|
||||
}
|
||||
));
|
||||
},
|
||||
),
|
||||
);
|
||||
} else {
|
||||
item.serve_rendered = false;
|
||||
}
|
||||
|
|
@ -184,9 +219,9 @@ export function server(opts) {
|
|||
}
|
||||
|
||||
startupPromises.push(
|
||||
serve_font(options, serving.fonts).then((sub) => {
|
||||
app.use('/', sub);
|
||||
})
|
||||
serve_font(options, serving.fonts).then((sub) => {
|
||||
app.use('/', sub);
|
||||
}),
|
||||
);
|
||||
|
||||
for (const id of Object.keys(data)) {
|
||||
|
|
@ -197,61 +232,65 @@ export function server(opts) {
|
|||
}
|
||||
|
||||
startupPromises.push(
|
||||
serve_data.add(options, serving.data, item, id, opts.publicUrl)
|
||||
serve_data.add(options, serving.data, item, id, opts.publicUrl),
|
||||
);
|
||||
}
|
||||
|
||||
if (options.serveAllStyles) {
|
||||
fs.readdir(options.paths.styles, {withFileTypes: true}, (err, files) => {
|
||||
fs.readdir(options.paths.styles, { withFileTypes: true }, (err, files) => {
|
||||
if (err) {
|
||||
return;
|
||||
}
|
||||
for (const file of files) {
|
||||
if (file.isFile() &&
|
||||
path.extname(file.name).toLowerCase() == '.json') {
|
||||
if (file.isFile() && path.extname(file.name).toLowerCase() == '.json') {
|
||||
const id = path.basename(file.name, '.json');
|
||||
const item = {
|
||||
style: file.name
|
||||
style: file.name,
|
||||
};
|
||||
addStyle(id, item, false, false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const watcher = chokidar.watch(path.join(options.paths.styles, '*.json'),
|
||||
{
|
||||
});
|
||||
watcher.on('all',
|
||||
(eventType, filename) => {
|
||||
if (filename) {
|
||||
const id = path.basename(filename, '.json');
|
||||
console.log(`Style "${id}" changed, updating...`);
|
||||
const watcher = chokidar.watch(
|
||||
path.join(options.paths.styles, '*.json'),
|
||||
{},
|
||||
);
|
||||
watcher.on('all', (eventType, filename) => {
|
||||
if (filename) {
|
||||
const id = path.basename(filename, '.json');
|
||||
console.log(`Style "${id}" changed, updating...`);
|
||||
|
||||
serve_style.remove(serving.styles, id);
|
||||
if (!isLight) {
|
||||
serve_rendered.remove(serving.rendered, id);
|
||||
}
|
||||
serve_style.remove(serving.styles, id);
|
||||
if (!isLight) {
|
||||
serve_rendered.remove(serving.rendered, id);
|
||||
}
|
||||
|
||||
if (eventType == 'add' || eventType == 'change') {
|
||||
const item = {
|
||||
style: filename
|
||||
};
|
||||
addStyle(id, item, false, false);
|
||||
}
|
||||
}
|
||||
});
|
||||
if (eventType == 'add' || eventType == 'change') {
|
||||
const item = {
|
||||
style: filename,
|
||||
};
|
||||
addStyle(id, item, false, false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
app.get('/styles.json', (req, res, next) => {
|
||||
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)) {
|
||||
const styleJSON = serving.styles[id].styleJSON;
|
||||
result.push({
|
||||
version: styleJSON.version,
|
||||
name: styleJSON.name,
|
||||
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);
|
||||
|
|
@ -266,9 +305,16 @@ export function server(opts) {
|
|||
} else {
|
||||
path = `${type}/${id}`;
|
||||
}
|
||||
info.tiles = getTileUrls(req, info.tiles, path, info.format, opts.publicUrl, {
|
||||
'pbf': options.pbfAlias
|
||||
});
|
||||
info.tiles = getTileUrls(
|
||||
req,
|
||||
info.tiles,
|
||||
path,
|
||||
info.format,
|
||||
opts.publicUrl,
|
||||
{
|
||||
pbf: options.pbfAlias,
|
||||
},
|
||||
);
|
||||
arr.push(info);
|
||||
}
|
||||
return arr;
|
||||
|
|
@ -294,40 +340,49 @@ export function server(opts) {
|
|||
if (template === 'index') {
|
||||
if (options.frontPage === false) {
|
||||
return;
|
||||
} else if (options.frontPage &&
|
||||
options.frontPage.constructor === String) {
|
||||
} else if (
|
||||
options.frontPage &&
|
||||
options.frontPage.constructor === String
|
||||
) {
|
||||
templateFile = path.resolve(paths.root, options.frontPage);
|
||||
}
|
||||
}
|
||||
startupPromises.push(new Promise((resolve, reject) => {
|
||||
fs.readFile(templateFile, (err, content) => {
|
||||
if (err) {
|
||||
err = new Error(`Template not found: ${err.message}`);
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
const compiled = handlebars.compile(content.toString());
|
||||
|
||||
app.use(urlPath, (req, res, next) => {
|
||||
let data = {};
|
||||
if (dataGetter) {
|
||||
data = dataGetter(req);
|
||||
if (!data) {
|
||||
return res.status(404).send('Not found');
|
||||
}
|
||||
startupPromises.push(
|
||||
new Promise((resolve, reject) => {
|
||||
fs.readFile(templateFile, (err, content) => {
|
||||
if (err) {
|
||||
err = new Error(`Template not found: ${err.message}`);
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
data['server_version'] = `${packageJson.name} v${packageJson.version}`;
|
||||
data['public_url'] = opts.publicUrl || '/';
|
||||
data['is_light'] = isLight;
|
||||
data['key_query_part'] =
|
||||
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');
|
||||
return res.status(200).send(compiled(data));
|
||||
const compiled = handlebars.compile(content.toString());
|
||||
|
||||
app.use(urlPath, (req, res, next) => {
|
||||
let data = {};
|
||||
if (dataGetter) {
|
||||
data = dataGetter(req);
|
||||
if (!data) {
|
||||
return res.status(404).send('Not found');
|
||||
}
|
||||
}
|
||||
data[
|
||||
'server_version'
|
||||
] = `${packageJson.name} v${packageJson.version}`;
|
||||
data['public_url'] = opts.publicUrl || '/';
|
||||
data['is_light'] = isLight;
|
||||
data['key_query_part'] = 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');
|
||||
return res.status(200).send(compiled(data));
|
||||
});
|
||||
resolve();
|
||||
});
|
||||
resolve();
|
||||
});
|
||||
}));
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
serveTemplate('/$', 'index', (req) => {
|
||||
|
|
@ -340,15 +395,23 @@ export function server(opts) {
|
|||
if (style.serving_rendered) {
|
||||
const center = style.serving_rendered.tileJSON.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]);
|
||||
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(
|
||||
req, style.serving_rendered.tileJSON.tiles,
|
||||
`styles/${id}`, style.serving_rendered.tileJSON.format, opts.publicUrl)[0];
|
||||
req,
|
||||
style.serving_rendered.tileJSON.tiles,
|
||||
`styles/${id}`,
|
||||
style.serving_rendered.tileJSON.format,
|
||||
opts.publicUrl,
|
||||
)[0];
|
||||
}
|
||||
}
|
||||
const data = clone(serving.data || {});
|
||||
|
|
@ -357,19 +420,29 @@ export function server(opts) {
|
|||
const tilejson = data[id].tileJSON;
|
||||
const center = tilejson.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';
|
||||
if (!data_.is_vector) {
|
||||
if (center) {
|
||||
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(
|
||||
req, tilejson.tiles, `data/${id}`, tilejson.format, opts.publicUrl, {
|
||||
'pbf': options.pbfAlias
|
||||
})[0];
|
||||
req,
|
||||
tilejson.tiles,
|
||||
`data/${id}`,
|
||||
tilejson.format,
|
||||
opts.publicUrl,
|
||||
{
|
||||
pbf: options.pbfAlias,
|
||||
},
|
||||
)[0];
|
||||
}
|
||||
if (data_.filesize) {
|
||||
let suffix = 'kB';
|
||||
|
|
@ -387,7 +460,7 @@ export function server(opts) {
|
|||
}
|
||||
return {
|
||||
styles: Object.keys(styles).length ? styles : null,
|
||||
data: Object.keys(data).length ? data : null
|
||||
data: Object.keys(data).length ? data : null,
|
||||
};
|
||||
});
|
||||
|
||||
|
|
@ -422,9 +495,12 @@ export function server(opts) {
|
|||
wmts.name = (serving.styles[id] || serving.rendered[id]).name;
|
||||
if (opts.publicUrl) {
|
||||
wmts.baseUrl = opts.publicUrl;
|
||||
}
|
||||
else {
|
||||
wmts.baseUrl = `${req.get('X-Forwarded-Protocol') ? req.get('X-Forwarded-Protocol') : req.protocol}://${req.get('host')}/`;
|
||||
} else {
|
||||
wmts.baseUrl = `${
|
||||
req.get('X-Forwarded-Protocol')
|
||||
? req.get('X-Forwarded-Protocol')
|
||||
: req.protocol
|
||||
}://${req.get('host')}/`;
|
||||
}
|
||||
return wmts;
|
||||
});
|
||||
|
|
@ -453,13 +529,17 @@ export function server(opts) {
|
|||
}
|
||||
});
|
||||
|
||||
const server = app.listen(process.env.PORT || opts.port, process.env.BIND || opts.bind, function() {
|
||||
let address = this.address().address;
|
||||
if (address.indexOf('::') === 0) {
|
||||
address = `[${address}]`; // literal IPv6 address
|
||||
}
|
||||
console.log(`Listening at http://${address}:${this.address().port}/`);
|
||||
});
|
||||
const server = app.listen(
|
||||
process.env.PORT || opts.port,
|
||||
process.env.BIND || opts.bind,
|
||||
function () {
|
||||
let address = this.address().address;
|
||||
if (address.indexOf('::') === 0) {
|
||||
address = `[${address}]`; // literal IPv6 address
|
||||
}
|
||||
console.log(`Listening at http://${address}:${this.address().port}/`);
|
||||
},
|
||||
);
|
||||
|
||||
// add server.shutdown() to gracefully stop serving
|
||||
enableShutdown(server);
|
||||
|
|
@ -467,7 +547,7 @@ export function server(opts) {
|
|||
return {
|
||||
app: app,
|
||||
server: server,
|
||||
startupPromise: startupPromise
|
||||
startupPromise: startupPromise,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -498,4 +578,4 @@ export const exports = (opts) => {
|
|||
});
|
||||
|
||||
return running;
|
||||
};
|
||||
};
|
||||
|
|
|
|||
115
src/utils.js
115
src/utils.js
|
|
@ -6,8 +6,8 @@ import fs from 'node:fs';
|
|||
import clone from 'clone';
|
||||
import glyphCompose from '@mapbox/glyph-pbf-composite';
|
||||
|
||||
|
||||
export const getPublicUrl = (publicUrl, req) => publicUrl || `${req.protocol}://${req.headers.host}/`;
|
||||
export const getPublicUrl = (publicUrl, req) =>
|
||||
publicUrl || `${req.protocol}://${req.headers.host}/`;
|
||||
|
||||
export const getTileUrls = (req, domains, path, format, publicUrl, aliases) => {
|
||||
if (domains) {
|
||||
|
|
@ -16,7 +16,8 @@ export const getTileUrls = (req, domains, path, format, publicUrl, aliases) => {
|
|||
}
|
||||
const host = req.headers.host;
|
||||
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);
|
||||
const newDomains = [];
|
||||
for (const domain of domains) {
|
||||
|
|
@ -43,7 +44,7 @@ export const getTileUrls = (req, domains, path, format, publicUrl, aliases) => {
|
|||
if (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]) {
|
||||
format = aliases[format];
|
||||
|
|
@ -52,7 +53,9 @@ export const getTileUrls = (req, domains, path, format, publicUrl, aliases) => {
|
|||
const uris = [];
|
||||
if (!publicUrl) {
|
||||
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 {
|
||||
uris.push(`${publicUrl}${path}/{z}/{x}/{y}.${format}${query}`);
|
||||
|
|
@ -69,59 +72,75 @@ export const fixTileJSONCenter = (tileJSON) => {
|
|||
(tileJSON.bounds[0] + tileJSON.bounds[2]) / 2,
|
||||
(tileJSON.bounds[1] + tileJSON.bounds[3]) / 2,
|
||||
Math.round(
|
||||
-Math.log((tileJSON.bounds[2] - tileJSON.bounds[0]) / 360 / tiles) /
|
||||
Math.LN2
|
||||
)
|
||||
-Math.log((tileJSON.bounds[2] - tileJSON.bounds[0]) / 360 / tiles) /
|
||||
Math.LN2,
|
||||
),
|
||||
];
|
||||
}
|
||||
};
|
||||
|
||||
const getFontPbf = (allowedFonts, fontPath, name, range, fallbacks) => new Promise((resolve, reject) => {
|
||||
if (!allowedFonts || (allowedFonts[name] && fallbacks)) {
|
||||
const filename = path.join(fontPath, name, `${range}.pbf`);
|
||||
if (!fallbacks) {
|
||||
fallbacks = clone(allowedFonts || {});
|
||||
}
|
||||
delete fallbacks[name];
|
||||
fs.readFile(filename, (err, data) => {
|
||||
if (err) {
|
||||
console.error(`ERROR: Font not found: ${name}`);
|
||||
if (fallbacks && Object.keys(fallbacks).length) {
|
||||
let fallbackName;
|
||||
|
||||
let fontStyle = name.split(' ').pop();
|
||||
if (['Regular', 'Bold', 'Italic'].indexOf(fontStyle) < 0) {
|
||||
fontStyle = 'Regular';
|
||||
}
|
||||
fallbackName = `Noto Sans ${fontStyle}`;
|
||||
if (!fallbacks[fallbackName]) {
|
||||
fallbackName = `Open Sans ${fontStyle}`;
|
||||
if (!fallbacks[fallbackName]) {
|
||||
fallbackName = Object.keys(fallbacks)[0];
|
||||
}
|
||||
}
|
||||
|
||||
console.error(`ERROR: Trying to use ${fallbackName} as a fallback`);
|
||||
delete fallbacks[fallbackName];
|
||||
getFontPbf(null, fontPath, fallbackName, range, fallbacks).then(resolve, reject);
|
||||
} else {
|
||||
reject(`Font load error: ${name}`);
|
||||
}
|
||||
} else {
|
||||
resolve(data);
|
||||
const getFontPbf = (allowedFonts, fontPath, name, range, fallbacks) =>
|
||||
new Promise((resolve, reject) => {
|
||||
if (!allowedFonts || (allowedFonts[name] && fallbacks)) {
|
||||
const filename = path.join(fontPath, name, `${range}.pbf`);
|
||||
if (!fallbacks) {
|
||||
fallbacks = clone(allowedFonts || {});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
reject(`Font not allowed: ${name}`);
|
||||
}
|
||||
});
|
||||
delete fallbacks[name];
|
||||
fs.readFile(filename, (err, data) => {
|
||||
if (err) {
|
||||
console.error(`ERROR: Font not found: ${name}`);
|
||||
if (fallbacks && Object.keys(fallbacks).length) {
|
||||
let fallbackName;
|
||||
|
||||
export const getFontsPbf = (allowedFonts, fontPath, names, range, fallbacks) => {
|
||||
let fontStyle = name.split(' ').pop();
|
||||
if (['Regular', 'Bold', 'Italic'].indexOf(fontStyle) < 0) {
|
||||
fontStyle = 'Regular';
|
||||
}
|
||||
fallbackName = `Noto Sans ${fontStyle}`;
|
||||
if (!fallbacks[fallbackName]) {
|
||||
fallbackName = `Open Sans ${fontStyle}`;
|
||||
if (!fallbacks[fallbackName]) {
|
||||
fallbackName = Object.keys(fallbacks)[0];
|
||||
}
|
||||
}
|
||||
|
||||
console.error(`ERROR: Trying to use ${fallbackName} as a fallback`);
|
||||
delete fallbacks[fallbackName];
|
||||
getFontPbf(null, fontPath, fallbackName, range, fallbacks).then(
|
||||
resolve,
|
||||
reject,
|
||||
);
|
||||
} else {
|
||||
reject(`Font load error: ${name}`);
|
||||
}
|
||||
} else {
|
||||
resolve(data);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
reject(`Font not allowed: ${name}`);
|
||||
}
|
||||
});
|
||||
|
||||
export const getFontsPbf = (
|
||||
allowedFonts,
|
||||
fontPath,
|
||||
names,
|
||||
range,
|
||||
fallbacks,
|
||||
) => {
|
||||
const fonts = names.split(',');
|
||||
const queue = [];
|
||||
for (const font of fonts) {
|
||||
queue.push(
|
||||
getFontPbf(allowedFonts, fontPath, font, range, clone(allowedFonts || fallbacks))
|
||||
getFontPbf(
|
||||
allowedFonts,
|
||||
fontPath,
|
||||
font,
|
||||
range,
|
||||
clone(allowedFonts || fallbacks),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,48 +1,48 @@
|
|||
const testTileJSONArray = function(url) {
|
||||
describe(url + ' is array of TileJSONs', function() {
|
||||
it('is json', function(done) {
|
||||
const testTileJSONArray = function (url) {
|
||||
describe(url + ' is array of TileJSONs', function () {
|
||||
it('is json', function (done) {
|
||||
supertest(app)
|
||||
.get(url)
|
||||
.expect(200)
|
||||
.expect('Content-Type', /application\/json/, done);
|
||||
.get(url)
|
||||
.expect(200)
|
||||
.expect('Content-Type', /application\/json/, done);
|
||||
});
|
||||
|
||||
it('is non-empty array', function(done) {
|
||||
it('is non-empty array', function (done) {
|
||||
supertest(app)
|
||||
.get(url)
|
||||
.expect(function(res) {
|
||||
expect(res.body).to.be.a('array');
|
||||
expect(res.body.length).to.be.greaterThan(0);
|
||||
}).end(done);
|
||||
.get(url)
|
||||
.expect(function (res) {
|
||||
expect(res.body).to.be.a('array');
|
||||
expect(res.body.length).to.be.greaterThan(0);
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const testTileJSON = function(url) {
|
||||
describe(url + ' is TileJSON', function() {
|
||||
it('is json', function(done) {
|
||||
const testTileJSON = function (url) {
|
||||
describe(url + ' is TileJSON', function () {
|
||||
it('is json', function (done) {
|
||||
supertest(app)
|
||||
.get(url)
|
||||
.expect(200)
|
||||
.expect('Content-Type', /application\/json/, done);
|
||||
.get(url)
|
||||
.expect(200)
|
||||
.expect('Content-Type', /application\/json/, done);
|
||||
});
|
||||
|
||||
it('has valid tiles', function(done) {
|
||||
it('has valid tiles', function (done) {
|
||||
supertest(app)
|
||||
.get(url)
|
||||
.expect(function(res) {
|
||||
expect(res.body.tiles.length).to.be.greaterThan(0);
|
||||
}).end(done);
|
||||
.get(url)
|
||||
.expect(function (res) {
|
||||
expect(res.body.tiles.length).to.be.greaterThan(0);
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
describe('Metadata', function() {
|
||||
describe('/health', function() {
|
||||
it('returns 200', function(done) {
|
||||
supertest(app)
|
||||
.get('/health')
|
||||
.expect(200, done);
|
||||
describe('Metadata', function () {
|
||||
describe('/health', function () {
|
||||
it('returns 200', function (done) {
|
||||
supertest(app).get('/health').expect(200, done);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -50,24 +50,25 @@ describe('Metadata', function() {
|
|||
testTileJSONArray('/rendered.json');
|
||||
testTileJSONArray('/data.json');
|
||||
|
||||
describe('/styles.json is valid array', function() {
|
||||
it('is json', function(done) {
|
||||
describe('/styles.json is valid array', function () {
|
||||
it('is json', function (done) {
|
||||
supertest(app)
|
||||
.get('/styles.json')
|
||||
.expect(200)
|
||||
.expect('Content-Type', /application\/json/, done);
|
||||
.get('/styles.json')
|
||||
.expect(200)
|
||||
.expect('Content-Type', /application\/json/, done);
|
||||
});
|
||||
|
||||
it('contains valid item', function(done) {
|
||||
it('contains valid item', function (done) {
|
||||
supertest(app)
|
||||
.get('/styles.json')
|
||||
.expect(function(res) {
|
||||
expect(res.body).to.be.a('array');
|
||||
expect(res.body.length).to.be.greaterThan(0);
|
||||
expect(res.body[0].version).to.be.equal(8);
|
||||
expect(res.body[0].id).to.be.a('string');
|
||||
expect(res.body[0].name).to.be.a('string');
|
||||
}).end(done);
|
||||
.get('/styles.json')
|
||||
.expect(function (res) {
|
||||
expect(res.body).to.be.a('array');
|
||||
expect(res.body.length).to.be.greaterThan(0);
|
||||
expect(res.body[0].version).to.be.equal(8);
|
||||
expect(res.body[0].id).to.be.a('string');
|
||||
expect(res.body[0].name).to.be.a('string');
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,28 +1,29 @@
|
|||
process.env.NODE_ENV = 'test';
|
||||
|
||||
import {expect} from 'chai';
|
||||
import { expect } from 'chai';
|
||||
import supertest from 'supertest';
|
||||
import {server} from '../src/server.js';
|
||||
import { server } from '../src/server.js';
|
||||
|
||||
global.expect = expect;
|
||||
global.supertest = supertest;
|
||||
|
||||
before(function() {
|
||||
before(function () {
|
||||
console.log('global setup');
|
||||
process.chdir('test_data');
|
||||
const running = server({
|
||||
configPath: 'config.json',
|
||||
port: 8888,
|
||||
publicUrl: '/test/'
|
||||
publicUrl: '/test/',
|
||||
});
|
||||
global.app = running.app;
|
||||
global.server = running.server;
|
||||
return running.startupPromise;
|
||||
});
|
||||
|
||||
after(function() {
|
||||
after(function () {
|
||||
console.log('global teardown');
|
||||
global.server.close(function() {
|
||||
console.log('Done'); process.exit();
|
||||
global.server.close(function () {
|
||||
console.log('Done');
|
||||
process.exit();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
156
test/static.js
156
test/static.js
|
|
@ -1,10 +1,10 @@
|
|||
const testStatic = function(prefix, q, format, status, scale, type, query) {
|
||||
const testStatic = function (prefix, q, format, status, scale, type, query) {
|
||||
if (scale) q += '@' + scale + 'x';
|
||||
let path = '/styles/' + prefix + '/static/' + q + '.' + format;
|
||||
if (query) {
|
||||
path += query;
|
||||
}
|
||||
it(path + ' returns ' + status, function(done) {
|
||||
it(path + ' returns ' + status, function (done) {
|
||||
const test = supertest(app).get(path);
|
||||
if (status) test.expect(status);
|
||||
if (type) test.expect('Content-Type', type);
|
||||
|
|
@ -14,17 +14,45 @@ const testStatic = function(prefix, q, format, status, scale, type, query) {
|
|||
|
||||
const prefix = 'test-style';
|
||||
|
||||
describe('Static endpoints', function() {
|
||||
describe('center-based', function() {
|
||||
describe('valid requests', function() {
|
||||
describe('various formats', function() {
|
||||
testStatic(prefix, '0,0,0/256x256', '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('Static endpoints', function () {
|
||||
describe('center-based', function () {
|
||||
describe('valid requests', function () {
|
||||
describe('various formats', function () {
|
||||
testStatic(
|
||||
prefix,
|
||||
'0,0,0/256x256',
|
||||
'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 () {
|
||||
testStatic(prefix, '0,0,0/300x300', 'png', 200, 2);
|
||||
testStatic(prefix, '0,0,0/300x300', 'png', 200, 3);
|
||||
|
||||
|
|
@ -42,7 +70,7 @@ describe('Static endpoints', function() {
|
|||
});
|
||||
});
|
||||
|
||||
describe('invalid requests return 4xx', function() {
|
||||
describe('invalid requests return 4xx', function () {
|
||||
testStatic(prefix, '190,0,0/256x256', 'png', 400);
|
||||
testStatic(prefix, '0,86,0/256x256', 'png', 400);
|
||||
testStatic(prefix, '80,40,20/0x0', 'png', 400);
|
||||
|
|
@ -57,16 +85,44 @@ describe('Static endpoints', function() {
|
|||
});
|
||||
});
|
||||
|
||||
describe('area-based', function() {
|
||||
describe('valid requests', function() {
|
||||
describe('various formats', function() {
|
||||
testStatic(prefix, '-180,-80,180,80/10x10', '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('area-based', function () {
|
||||
describe('valid requests', function () {
|
||||
describe('various formats', function () {
|
||||
testStatic(
|
||||
prefix,
|
||||
'-180,-80,180,80/10x10',
|
||||
'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 () {
|
||||
testStatic(prefix, '-180,-90,180,90/20x20', 'png', 200, 2);
|
||||
testStatic(prefix, '0,0,1,1/200x200', 'png', 200, 3);
|
||||
|
||||
|
|
@ -74,7 +130,7 @@ describe('Static endpoints', function() {
|
|||
});
|
||||
});
|
||||
|
||||
describe('invalid requests return 4xx', function() {
|
||||
describe('invalid requests return 4xx', function () {
|
||||
testStatic(prefix, '0,87,1,88/5x2', 'png', 400);
|
||||
|
||||
testStatic(prefix, '0,0,1,1/1x1', 'gif', 400);
|
||||
|
|
@ -83,20 +139,60 @@ describe('Static endpoints', function() {
|
|||
});
|
||||
});
|
||||
|
||||
describe('autofit path', function() {
|
||||
describe('valid requests', function() {
|
||||
testStatic(prefix, 'auto/256x256', 'png', 200, undefined, /image\/png/, '?path=10,10|20,20');
|
||||
describe('autofit path', function () {
|
||||
describe('valid requests', function () {
|
||||
testStatic(
|
||||
prefix,
|
||||
'auto/256x256',
|
||||
'png',
|
||||
200,
|
||||
undefined,
|
||||
/image\/png/,
|
||||
'?path=10,10|20,20',
|
||||
);
|
||||
|
||||
describe('different parameters', function() {
|
||||
testStatic(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('different parameters', function () {
|
||||
testStatic(
|
||||
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, undefined, undefined, '?path=10,10');
|
||||
testStatic(prefix, 'auto/2560x2560', 'png', 400, undefined, undefined, '?path=10,10|20,20');
|
||||
testStatic(
|
||||
prefix,
|
||||
'auto/256x256',
|
||||
'png',
|
||||
400,
|
||||
undefined,
|
||||
undefined,
|
||||
'?path=10,10',
|
||||
);
|
||||
testStatic(
|
||||
prefix,
|
||||
'auto/2560x2560',
|
||||
'png',
|
||||
400,
|
||||
undefined,
|
||||
undefined,
|
||||
'?path=10,10|20,20',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,38 +1,41 @@
|
|||
const testIs = function(url, type, status) {
|
||||
it(url + ' return ' + (status || 200) + ' and is ' + type.toString(),
|
||||
function(done) {
|
||||
supertest(app)
|
||||
.get(url)
|
||||
.expect(status || 200)
|
||||
.expect('Content-Type', type, done);
|
||||
});
|
||||
const testIs = function (url, type, status) {
|
||||
it(
|
||||
url + ' return ' + (status || 200) + ' and is ' + type.toString(),
|
||||
function (done) {
|
||||
supertest(app)
|
||||
.get(url)
|
||||
.expect(status || 200)
|
||||
.expect('Content-Type', type, done);
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
const prefix = 'test-style';
|
||||
|
||||
describe('Styles', function() {
|
||||
describe('/styles/' + prefix + '/style.json is valid style', function() {
|
||||
describe('Styles', function () {
|
||||
describe('/styles/' + prefix + '/style.json is valid style', function () {
|
||||
testIs('/styles/' + prefix + '/style.json', /application\/json/);
|
||||
|
||||
it('contains expected properties', function(done) {
|
||||
it('contains expected properties', function (done) {
|
||||
supertest(app)
|
||||
.get('/styles/' + prefix + '/style.json')
|
||||
.expect(function(res) {
|
||||
expect(res.body.version).to.be.equal(8);
|
||||
expect(res.body.name).to.be.a('string');
|
||||
expect(res.body.sources).to.be.a('object');
|
||||
expect(res.body.glyphs).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.layers).to.be.a('array');
|
||||
}).end(done);
|
||||
.get('/styles/' + prefix + '/style.json')
|
||||
.expect(function (res) {
|
||||
expect(res.body.version).to.be.equal(8);
|
||||
expect(res.body.name).to.be.a('string');
|
||||
expect(res.body.sources).to.be.a('object');
|
||||
expect(res.body.glyphs).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.layers).to.be.a('array');
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
});
|
||||
describe('/styles/streets/style.json is not served', function() {
|
||||
describe('/styles/streets/style.json is not served', function () {
|
||||
testIs('/styles/streets/style.json', /./, 404);
|
||||
});
|
||||
|
||||
describe('/styles/' + prefix + '/sprite[@2x].{format}', function() {
|
||||
describe('/styles/' + prefix + '/sprite[@2x].{format}', function () {
|
||||
testIs('/styles/' + prefix + '/sprite.json', /application\/json/);
|
||||
testIs('/styles/' + prefix + '/sprite@2x.json', /application\/json/);
|
||||
testIs('/styles/' + prefix + '/sprite.png', /image\/png/);
|
||||
|
|
@ -40,11 +43,13 @@ describe('Styles', function() {
|
|||
});
|
||||
});
|
||||
|
||||
describe('Fonts', function() {
|
||||
describe('Fonts', function () {
|
||||
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 Bold,Open Sans Regular/0-255.pbf',
|
||||
/application\/x-protobuf/);
|
||||
testIs(
|
||||
'/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/0-255.pbf', /./, 400);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
const testTile = function(prefix, z, x, y, status) {
|
||||
const testTile = function (prefix, z, x, y, status) {
|
||||
const path = '/data/' + prefix + '/' + z + '/' + x + '/' + y + '.pbf';
|
||||
it(path + ' returns ' + status, function(done) {
|
||||
it(path + ' returns ' + status, function (done) {
|
||||
const test = supertest(app).get(path);
|
||||
if (status) test.expect(status);
|
||||
if (status == 200) test.expect('Content-Type', /application\/x-protobuf/);
|
||||
|
|
@ -10,13 +10,13 @@ const testTile = function(prefix, z, x, y, status) {
|
|||
|
||||
const prefix = 'openmaptiles';
|
||||
|
||||
describe('Vector tiles', function() {
|
||||
describe('existing tiles', function() {
|
||||
describe('Vector tiles', function () {
|
||||
describe('existing tiles', function () {
|
||||
testTile(prefix, 0, 0, 0, 200);
|
||||
testTile(prefix, 14, 8581, 5738, 200);
|
||||
});
|
||||
|
||||
describe('non-existent requests return 4xx', function() {
|
||||
describe('non-existent requests return 4xx', function () {
|
||||
testTile('non_existent', 0, 0, 0, 404);
|
||||
testTile(prefix, -1, 0, 0, 404); // err zoom
|
||||
testTile(prefix, 20, 0, 0, 404); // zoom out of bounds
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
const testTile = function(prefix, z, x, y, format, status, scale, type) {
|
||||
const testTile = function (prefix, z, x, y, format, status, scale, type) {
|
||||
if (scale) y += '@' + scale + 'x';
|
||||
const path = '/styles/' + prefix + '/' + z + '/' + x + '/' + y + '.' + format;
|
||||
it(path + ' returns ' + status, function(done) {
|
||||
it(path + ' returns ' + status, function (done) {
|
||||
const test = supertest(app).get(path);
|
||||
test.expect(status);
|
||||
if (type) test.expect('Content-Type', type);
|
||||
|
|
@ -11,16 +11,16 @@ const testTile = function(prefix, z, x, y, format, status, scale, type) {
|
|||
|
||||
const prefix = 'test-style';
|
||||
|
||||
describe('Raster tiles', function() {
|
||||
describe('valid requests', function() {
|
||||
describe('various formats', function() {
|
||||
describe('Raster tiles', function () {
|
||||
describe('valid requests', function () {
|
||||
describe('various formats', function () {
|
||||
testTile(prefix, 0, 0, 0, 'png', 200, undefined, /image\/png/);
|
||||
testTile(prefix, 0, 0, 0, 'jpg', 200, undefined, /image\/jpeg/);
|
||||
testTile(prefix, 0, 0, 0, 'jpeg', 200, undefined, /image\/jpeg/);
|
||||
testTile(prefix, 0, 0, 0, 'webp', 200, undefined, /image\/webp/);
|
||||
});
|
||||
|
||||
describe('different coordinates and scales', function() {
|
||||
describe('different coordinates and scales', function () {
|
||||
testTile(prefix, 1, 1, 1, 'png', 200);
|
||||
|
||||
testTile(prefix, 0, 0, 0, 'png', 200, 2);
|
||||
|
|
@ -29,7 +29,7 @@ describe('Raster tiles', function() {
|
|||
});
|
||||
});
|
||||
|
||||
describe('invalid requests return 4xx', function() {
|
||||
describe('invalid requests return 4xx', function () {
|
||||
testTile('non_existent', 0, 0, 0, 'png', 404);
|
||||
testTile(prefix, -1, 0, 0, 'png', 404);
|
||||
testTile(prefix, 25, 0, 0, 'png', 404);
|
||||
|
|
|
|||
Loading…
Reference in a new issue