tileserver-gl/src/main.js
Andrew Calcutt 97be9db6b7
Upgrade Express to v5 +Canvas v3 + code cleanup (#1429)
* first attempt to upgrade express to v5

Co-Authored-By: Andrew Calcutt <acalcutt@techidiots.net>

* try to fix https://github.com/maptiler/tileserver-gl/issues/1411

Co-Authored-By: Andrew Calcutt <acalcutt@techidiots.net>

* cleanup server.js

Co-Authored-By: Andrew Calcutt <acalcutt@techidiots.net>

* cleanup serve_font.js

Co-Authored-By: Andrew Calcutt <acalcutt@techidiots.net>

* cleanup sever_rendered.js

Co-Authored-By: Andrew Calcutt <acalcutt@techidiots.net>

* cleanup server_data.js

Co-Authored-By: Andrew Calcutt <acalcutt@techidiots.net>

* cleanup serve_style

Co-Authored-By: Andrew Calcutt <acalcutt@techidiots.net>

* Update serve_style.js

Co-Authored-By: Andrew Calcutt <acalcutt@techidiots.net>

* Move UV_THREADPOOL_SIZE  to main thred

Co-Authored-By: Andrew Calcutt <acalcutt@techidiots.net>

* cleanup utils.js

Co-Authored-By: Andrew Calcutt <acalcutt@techidiots.net>

* Use common app.get for images and static images

Co-Authored-By: Andrew Calcutt <acalcutt@techidiots.net>

* add allowedTileSizes and option

Co-Authored-By: Andrew Calcutt <acalcutt@techidiots.net>

* cleanup error responses

Co-Authored-By: Andrew Calcutt <acalcutt@techidiots.net>

* fix /style/id.json with next('route')

Co-Authored-By: Andrew Calcutt <acalcutt@techidiots.net>

* improve sprite path

Co-Authored-By: Andrew Calcutt <acalcutt@techidiots.net>

* add parseFloadts around zxy

Co-Authored-By: Andrew Calcutt <acalcutt@techidiots.net>

* simplify server_data

Co-Authored-By: Andrew Calcutt <acalcutt@techidiots.net>

* move tile fetch and add fix verbose logging

Co-Authored-By: Andrew Calcutt <acalcutt@techidiots.net>

* add Handling request to verbose logging

Co-Authored-By: Andrew Calcutt <acalcutt@techidiots.net>

* first attempt to upgrade express to v5

Co-Authored-By: Andrew Calcutt <acalcutt@techidiots.net>

* try to fix https://github.com/maptiler/tileserver-gl/issues/1411

Co-Authored-By: Andrew Calcutt <acalcutt@techidiots.net>

* cleanup server.js

Co-Authored-By: Andrew Calcutt <acalcutt@techidiots.net>

* cleanup serve_font.js

Co-Authored-By: Andrew Calcutt <acalcutt@techidiots.net>

* cleanup sever_rendered.js

Co-Authored-By: Andrew Calcutt <acalcutt@techidiots.net>

* cleanup server_data.js

Co-Authored-By: Andrew Calcutt <acalcutt@techidiots.net>

* cleanup serve_style

Co-Authored-By: Andrew Calcutt <acalcutt@techidiots.net>

* Update serve_style.js

Co-Authored-By: Andrew Calcutt <acalcutt@techidiots.net>

* Move UV_THREADPOOL_SIZE  to main thred

Co-Authored-By: Andrew Calcutt <acalcutt@techidiots.net>

* cleanup utils.js

Co-Authored-By: Andrew Calcutt <acalcutt@techidiots.net>

* Use common app.get for images and static images

Co-Authored-By: Andrew Calcutt <acalcutt@techidiots.net>

* add allowedTileSizes and option

Co-Authored-By: Andrew Calcutt <acalcutt@techidiots.net>

* cleanup error responses

Co-Authored-By: Andrew Calcutt <acalcutt@techidiots.net>

* fix /style/id.json with next('route')

Co-Authored-By: Andrew Calcutt <acalcutt@techidiots.net>

* improve sprite path

Co-Authored-By: Andrew Calcutt <acalcutt@techidiots.net>

* add parseFloadts around zxy

Co-Authored-By: Andrew Calcutt <acalcutt@techidiots.net>

* simplify server_data

Co-Authored-By: Andrew Calcutt <acalcutt@techidiots.net>

* move tile fetch and add fix verbose logging

Co-Authored-By: Andrew Calcutt <acalcutt@techidiots.net>

* add Handling request to verbose logging

Co-Authored-By: Andrew Calcutt <acalcutt@techidiots.net>

* merge elevation changes

* lint format

* add verbose logging, improve headers

* try to fix codeql

Information exposure through a stack trace

* test

* all tests passing

* cleanup unneeded changes

* cleanup

* try to fix codeql error

* font fixes

* fix tile size issue

* try to improve scale + codeql

* codeql for sprite logging

* codeql serve fonts

* codeql fixes

* fix failing test with multiple fonts

* Update serve_font.js

* codeql

* codeql

* codeql

* Update utils.js

* codeql

* codeql

* codeql

* codeql

* codeql sanitize

* Update serve_font.js

* Update serve_font.js

* remove useless assignment

* move isGzipped

* add if-modified-since and cache-control

* use consistent cache control

* reformat

* codeql

* codeql

* codeql

* codeql

* codeql

* codeql

* codeql

* Update serve_font.js

* Update serve_font.js

* Update serve_font.js

* Update serve_style.js

* Update serve_style.js

* Update serve_style.js

* Revert "Update serve_style.js"

This reverts commit e0574b1887.

* Revert "Update serve_style.js"

This reverts commit b1e1d72f25.

* Revert "Update serve_style.js"

This reverts commit 0f3629c752.

* Add readFile function

* use readFile, add path.normalize

* Update serve_rendered.js

* simplify input checking

* Update utils.js

* codeql

* Revert "codeql"

This reverts commit e18874fda0.

* Revert "Update utils.js"

This reverts commit 5de617dfe2.

* Revert "simplify input checking"

This reverts commit 62a3212629.

* move allowed functions to utils.js

* use xy[0],xy[1],

* uprade canvas per https://github.com/maptiler/tileserver-gl/issues/1433

* make font regex less restrictive

* fix regex error

* Add version and changelog

* Update CHANGELOG.md

* Update CHANGELOG.md
2025-01-10 19:34:17 -05:00

300 lines
8.9 KiB
JavaScript

#!/usr/bin/env node
'use strict';
import os from 'os';
const envSize = parseInt(process.env.UV_THREADPOOL_SIZE, 10);
process.env.UV_THREADPOOL_SIZE = Math.ceil(
Math.max(4, isNaN(envSize) ? os.cpus().length * 1.5 : envSize),
);
import fs from 'node:fs';
import fsp from 'node:fs/promises';
import path from 'path';
import { fileURLToPath } from 'url';
import axios from 'axios';
import { server } from './server.js';
import { isValidHttpUrl } from './utils.js';
import { openPMtiles, getPMtilesInfo } from './pmtiles_adapter.js';
import { program } from 'commander';
import { existsP } from './promises.js';
import { openMbTilesWrapper } from './mbtiles_wrapper.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
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');
}
program
.description('tileserver-gl startup options')
.usage('tileserver-gl [mbtiles] [options]')
.option(
'--file <file>',
'MBTiles or PMTiles file\n' +
'\t ignored if the configuration file is also specified',
)
.option(
'--mbtiles <file>',
'(DEPRECIATED) MBTiles file\n' +
'\t ignored if 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')
.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)')
.option(
'-f|--log_format <format>',
'define the log format: https://github.com/expressjs/morgan#morganformat-options',
)
.version(packageJson.version, '-v, --version');
program.parse(process.argv);
const opts = program.opts();
console.log(`Starting ${packageJson.name} v${packageJson.version}`);
const startServer = (configPath, config) => {
let publicUrl = opts.public_url;
if (publicUrl && publicUrl.lastIndexOf('/') !== publicUrl.length - 1) {
publicUrl += '/';
}
return server({
configPath,
config,
bind: opts.bind,
port: opts.port,
cors: opts.cors,
verbose: opts.verbose,
silent: opts.silent,
logFile: opts.log_file,
logFormat: opts.log_format,
publicUrl,
});
};
const startWithInputFile = async (inputFile) => {
console.log(`[INFO] Automatically creating config file for ${inputFile}`);
console.log(`[INFO] Only a basic preview style will be used.`);
console.log(
`[INFO] See documentation to learn how to create config.json file.`,
);
let inputFilePath;
if (isValidHttpUrl(inputFile)) {
inputFilePath = process.cwd();
} else {
inputFile = path.resolve(process.cwd(), inputFile);
inputFilePath = path.dirname(inputFile);
const inputFileStats = await fsp.stat(inputFile);
if (!inputFileStats.isFile() || inputFileStats.size === 0) {
console.log(`ERROR: Not a valid input file: `);
process.exit(1);
}
}
const styleDir = path.resolve(
__dirname,
'../node_modules/tileserver-gl-styles/',
);
const config = {
options: {
paths: {
root: styleDir,
fonts: 'fonts',
styles: 'styles',
mbtiles: inputFilePath,
pmtiles: inputFilePath,
},
},
styles: {},
data: {},
};
const extension = inputFile.split('.').pop().toLowerCase();
if (extension === 'pmtiles') {
const fileOpenInfo = openPMtiles(inputFile);
const metadata = await getPMtilesInfo(fileOpenInfo);
if (
metadata.format === 'pbf' &&
metadata.name.toLowerCase().indexOf('openmaptiles') > -1
) {
if (isValidHttpUrl(inputFile)) {
config['data'][`v3`] = {
pmtiles: inputFile,
};
} else {
config['data'][`v3`] = {
pmtiles: path.basename(inputFile),
};
}
const styles = await fsp.readdir(path.resolve(styleDir, 'styles'));
for (const styleName of styles) {
const styleFileRel = styleName + '/style.json';
const styleFile = path.resolve(styleDir, 'styles', styleFileRel);
if (await existsP(styleFile)) {
config['styles'][styleName] = {
style: styleFileRel,
tilejson: {
bounds: metadata.bounds,
},
};
}
}
} else {
console.log(
`WARN: PMTiles not in "openmaptiles" format. Serving raw data only...`,
);
if (isValidHttpUrl(inputFile)) {
config['data'][(metadata.id || 'pmtiles').replace(/[?/:]/g, '_')] = {
pmtiles: inputFile,
};
} else {
config['data'][(metadata.id || 'pmtiles').replace(/[?/:]/g, '_')] = {
pmtiles: path.basename(inputFile),
};
}
}
if (opts.verbose) {
console.log(JSON.stringify(config, undefined, 2));
} else {
console.log('Run with --verbose to see the config file here.');
}
return startServer(null, config);
} else {
if (isValidHttpUrl(inputFile)) {
console.log(
`ERROR: MBTiles does not support web based files. "${inputFile}" is not a valid data file.`,
);
process.exit(1);
}
let info;
try {
const mbw = await openMbTilesWrapper(inputFile);
info = await mbw.getInfo();
if (!info) throw new Error('Metadata missing in the MBTiles.');
} catch (err) {
console.log('ERROR: Unable to open MBTiles or read metadata:', err);
console.log(`Make sure ${path.basename(inputFile)} is valid MBTiles.`);
process.exit(1);
}
const bounds = info.bounds;
if (
info.format === 'pbf' &&
info.name.toLowerCase().indexOf('openmaptiles') > -1
) {
config['data'][`v3`] = {
mbtiles: path.basename(inputFile),
};
const styles = await fsp.readdir(path.resolve(styleDir, 'styles'));
for (const styleName of styles) {
const styleFileRel = styleName + '/style.json';
const styleFile = path.resolve(styleDir, 'styles', styleFileRel);
if (await existsP(styleFile)) {
config['styles'][styleName] = {
style: styleFileRel,
tilejson: {
bounds,
},
};
}
}
} else {
console.log(
`WARN: MBTiles not in "openmaptiles" format. Serving raw data only...`,
);
config['data'][(info.id || 'mbtiles').replace(/[?/:]/g, '_')] = {
mbtiles: path.basename(inputFile),
};
}
if (opts.verbose) {
console.log(JSON.stringify(config, undefined, 2));
} else {
console.log('Run with --verbose to see the config file here.');
}
return startServer(null, config);
}
};
fs.stat(path.resolve(opts.config), async (err, stats) => {
if (err || !stats.isFile() || stats.size === 0) {
let inputFile;
if (opts.file) {
inputFile = opts.file;
} else if (opts.mbtiles) {
inputFile = opts.mbtiles;
}
if (inputFile) {
return startWithInputFile(inputFile);
} else {
// try to find in the cwd
const files = await fsp.readdir(process.cwd());
for (const filename of files) {
if (filename.endsWith('.mbtiles') || filename.endsWith('.pmtiles')) {
const inputFilesStats = await fsp.stat(filename);
if (inputFilesStats.isFile() && inputFilesStats.size > 0) {
inputFile = filename;
break;
}
}
}
if (inputFile) {
console.log(`No input file specified, using ${inputFile}`);
return startWithInputFile(inputFile);
} else {
const url =
'https://github.com/maptiler/tileserver-gl/releases/download/v1.3.0/zurich_switzerland.mbtiles';
const filename = 'zurich_switzerland.mbtiles';
const writer = fs.createWriteStream(filename);
console.log(`No input file found`);
console.log(`[DEMO] Downloading sample data (${filename}) from ${url}`);
try {
const response = await axios({
url,
method: 'GET',
responseType: 'stream',
});
response.data.pipe(writer);
writer.on('finish', () => startWithInputFile(filename));
writer.on('error', (err) =>
console.error(`Error writing file: ${err}`),
);
} catch (error) {
console.error(`Error downloading file: ${error}`);
}
}
}
} else {
console.log(`Using specified config file from ${opts.config}`);
return startServer(opts.config, null);
}
});