add verbose logging, improve headers

This commit is contained in:
acalcutt 2025-01-02 22:08:12 -05:00
parent 71335872bc
commit 0d72d5796a
6 changed files with 207 additions and 43 deletions

View file

@ -17,11 +17,7 @@ import {
isValidHttpUrl,
fetchTileData,
} from './utils.js';
import {
getPMtilesInfo,
getPMtilesTile,
openPMtiles,
} from './pmtiles_adapter.js';
import { getPMtilesInfo, openPMtiles } from './pmtiles_adapter.js';
import { gunzipP, gzipP } from './promises.js';
import { openMbTilesWrapper } from './mbtiles_wrapper.js';
@ -30,12 +26,28 @@ export const serve_data = {
* Initializes the serve_data module.
* @param {object} options Configuration options.
* @param {object} repo Repository object.
* @param {object} programOpts - An object containing the program options
* @returns {express.Application} The initialized Express application.
*/
init: function (options, repo) {
init: function (options, repo, programOpts) {
const { verbose } = programOpts;
const app = express().disable('x-powered-by');
/**
* Handles requests for tile data, responding with the tile image.
* @param {object} req - Express request object.
* @param {object} res - Express response object.
* @param {string} req.params.id - ID of the tile.
* @param {string} req.params.z - Z coordinate of the tile.
* @param {string} req.params.x - X coordinate of the tile.
* @param {string} req.params.y - Y coordinate of the tile.
* @param {string} req.params.format - Format of the tile.
* @returns {Promise<void>}
*/
app.get('/:id/:z/:x/:y.:format', async (req, res) => {
if (verbose) {
console.log(req.params);
}
const item = repo[req.params.id];
if (!item) {
return res.sendStatus(404);
@ -88,7 +100,14 @@ export const serve_data = {
data = await gunzipP(data);
isGzipped = false;
}
data = options.dataDecoratorFunc(id, 'data', data, z, x, y);
data = options.dataDecoratorFunc(
req.params.id,
'data',
data,
z,
x,
y,
);
}
}
@ -112,7 +131,9 @@ export const serve_data = {
}
data = JSON.stringify(geojson);
}
delete headers['ETag']; // do not trust the tile ETag -- regenerate
if (headers) {
delete headers['ETag'];
}
headers['Content-Encoding'] = 'gzip';
res.set(headers);
@ -123,6 +144,16 @@ export const serve_data = {
return res.status(200).send(data);
});
/**
* Handles requests for elevation data.
* @param {object} req - Express request object.
* @param {object} res - Express response object.
* @param {string} req.params.id - ID of the elevation data.
* @param {string} req.params.z - Z coordinate of the tile.
* @param {string} req.params.x - X coordinate of the tile (either integer or float).
* @param {string} req.params.y - Y coordinate of the tile (either integer or float).
* @returns {Promise<void>}
*/
app.get('/:id/elevation/:z/:x/:y', async (req, res, next) => {
try {
const item = repo?.[req.params.id];
@ -189,6 +220,7 @@ export const serve_data = {
const { minX, minY } = new SphericalMercator().xyz(tileCenter, z);
xy = [minX, minY];
}
const fetchTile = await fetchTileData(source, sourceType, z, x, y);
if (fetchTile == null) return res.status(204).send();
@ -259,6 +291,13 @@ export const serve_data = {
}
});
/**
* Handles requests for metadata for the tiles.
* @param {object} req - Express request object.
* @param {object} res - Express response object.
* @param {string} req.params.id - ID of the data source.
* @returns {Promise<void>}
*/
app.get('/:id.json', (req, res) => {
const item = repo[req.params.id];
if (!item) {
@ -289,6 +328,9 @@ export const serve_data = {
* @param {object} params Parameters object.
* @param {string} id ID of the data source.
* @param {object} programOpts - An object containing the program options
* @param {string} programOpts.publicUrl Public URL for the data.
* @param {boolean} programOpts.verbose Whether verbose logging should be used.
* @param {Function} dataResolver Function to resolve data.
* @returns {Promise<void>}
*/
add: async function (options, repo, params, id, programOpts) {

View file

@ -8,9 +8,11 @@ import { getFontsPbf, listFonts } from './utils.js';
* Initializes and returns an Express app that serves font files.
* @param {object} options - Configuration options for the server.
* @param {object} allowedFonts - An object containing allowed fonts.
* @param {object} programOpts - An object containing the program options.
* @returns {Promise<express.Application>} - A promise that resolves to the Express app.
*/
export async function serve_font(options, allowedFonts) {
export async function serve_font(options, allowedFonts, programOpts) {
const { verbose } = programOpts;
const app = express().disable('x-powered-by');
const lastModified = new Date().toUTCString();
@ -19,7 +21,18 @@ export async function serve_font(options, allowedFonts) {
const existingFonts = {};
/**
* Handles requests for a font file.
* @param {object} req - Express request object.
* @param {object} res - Express response object.
* @param {string} req.params.fontstack - Name of the font stack.
* @param {string} req.params.range - The range of the font (e.g. 0-255).
* @returns {Promise<void>}
*/
app.get('/fonts/:fontstack/:range.pbf', async (req, res) => {
if (verbose) {
console.log(req.params);
}
const fontstack = decodeURI(req.params.fontstack);
const range = req.params.range;
@ -41,6 +54,12 @@ export async function serve_font(options, allowedFonts) {
}
});
/**
* Handles requests for a list of all available fonts.
* @param {object} req - Express request object.
* @param {object} res - Express response object.
* @returns {void}
*/
app.get('/fonts.json', (req, res) => {
res.header('Content-type', 'application/json');
return res.send(

View file

@ -3,7 +3,7 @@
'use strict';
export const serve_rendered = {
init: (options, repo) => {},
add: (options, repo, params, id, publicUrl, dataResolver) => {},
init: (options, repo, programOpts) => {},
add: (options, repo, params, id, programOpts, dataResolver) => {},
remove: (repo, id) => {},
};

View file

@ -267,7 +267,6 @@ function extractPathsFromQuery(query, transformer) {
}
return paths;
}
/**
* Parses marker options provided via query and sets corresponding attributes
* on marker object.
@ -626,6 +625,13 @@ const respondImage = async (
* @param {object} options - Configuration options for the server.
* @param {object} repo - The repository object holding style data.
* @param {object} req - Express request object.
* @param {string} req.params.id - The id of the style.
* @param {string} req.params.p1 - The tile size parameter, if available.
* @param {string} req.params.p2 - The z parameter.
* @param {string} req.params.p3 - The x parameter.
* @param {string} req.params.p4 - The y parameter.
* @param {string} req.params.scale - The scale parameter.
* @param {string} req.params.format - The format of the image.
* @param {object} res - Express response object.
* @param {Function} next - Express next middleware function.
* @param {number} maxScaleFactor - The maximum scale factor allowed.
@ -641,12 +647,12 @@ async function handleTileRequest(
) {
const {
id,
p1: tileSize,
p2: zParam,
p3: xParam,
p4: yParam,
scale: scaleParam,
format,
p1: tileSize,
} = req.params;
const item = repo[id];
if (!item) {
@ -694,6 +700,12 @@ async function handleTileRequest(
* @param {object} repo - The repository object holding style data.
* @param {object} req - Express request object.
* @param {object} res - Express response object.
* @param {string} req.params.p2 - The raw or static parameter.
* @param {string} req.params.p3 - The staticType parameter.
* @param {string} req.params.p4 - The width parameter.
* @param {string} req.params.p5 - The height parameter.
* @param {string} req.params.scale - The scale parameter.
* @param {string} req.params.format - The format of the image.
* @param {Function} next - Express next middleware function.
* @param {number} maxScaleFactor - The maximum scale factor allowed.
* @returns {Promise<void>}
@ -708,32 +720,24 @@ async function handleStaticRequest(
) {
const {
id,
scale: scaleParam,
format,
p2: raw,
p3: type,
p3: staticType,
p4: width,
p5: height,
scale: scaleParam,
format,
} = req.params;
const item = repo[id];
const parsedWidth = parseInt(width) || 512;
const parsedHeight = parseInt(height) || 512;
const scale = parseScale(scaleParam, maxScaleFactor);
let isRaw = raw !== undefined;
let staticType = type;
if (!staticType) {
//workaround for type when raw is not set
staticType = raw;
isRaw = false;
}
let isRaw = raw === 'raw';
if (!item || !staticType || !format || !scale) {
return res.sendStatus(404);
}
const staticTypeMatch = staticType.match(staticTypeRegex);
console.log(staticTypeMatch);
if (staticTypeMatch.groups.lon) {
// Center Based Static Image
const z = parseFloat(staticTypeMatch.groups.zoom) || 0;
@ -765,7 +769,7 @@ async function handleStaticRequest(
// prettier-ignore
return await respondImage(
options, item, z, x, y, bearing, pitch, parsedWidth, parsedHeight, scale, format, res, overlay, 'static',
options, item, z, x, y, bearing, pitch, parsedWidth, parsedHeight, scale, format, res, overlay, 'static',
);
} else if (staticTypeMatch.groups.minx) {
// Area Based Static Image
@ -800,12 +804,12 @@ async function handleStaticRequest(
const markers = extractMarkersFromQuery(req.query, options, transformer);
// prettier-ignore
const overlay = await renderOverlay(
z, x, y, bearing, pitch, parsedWidth, parsedHeight, scale, paths, markers, req.query,
z, x, y, bearing, pitch, parsedWidth, parsedHeight, scale, paths, markers, req.query,
);
// prettier-ignore
return await respondImage(
options, item, z, x, y, bearing, pitch, parsedWidth, parsedHeight, scale, format, res, overlay, 'static',
options, item, z, x, y, bearing, pitch, parsedWidth, parsedHeight, scale, format, res, overlay, 'static',
);
} else if (staticTypeMatch.groups.auto) {
// Area Static Image
@ -859,12 +863,12 @@ async function handleStaticRequest(
// prettier-ignore
const overlay = await renderOverlay(
z, x, y, bearing, pitch, parsedWidth, parsedHeight, scale, paths, markers, req.query,
z, x, y, bearing, pitch, parsedWidth, parsedHeight, scale, paths, markers, req.query,
);
// prettier-ignore
return await respondImage(
options, item, z, x, y, bearing, pitch, parsedWidth, parsedHeight, scale, format, res, overlay, 'static',
options, item, z, x, y, bearing, pitch, parsedWidth, parsedHeight, scale, format, res, overlay, 'static',
);
} else {
return res.sendStatus(404);
@ -879,18 +883,37 @@ export const serve_rendered = {
* Initializes the serve_rendered module.
* @param {object} options Configuration options.
* @param {object} repo Repository object.
* @param {object} programOpts - An object containing the program options.
* @returns {Promise<express.Application>} A promise that resolves to the Express app.
*/
init: async function (options, repo) {
init: async function (options, repo, programOpts) {
const { verbose } = programOpts;
maxScaleFactor = Math.min(Math.floor(options.maxScaleFactor || 3), 9);
const app = express().disable('x-powered-by');
/**
* Handles requests for tile images.
* @param {object} req - Express request object.
* @param {object} res - Express response object.
* @param {string} req.params.id - The id of the style.
* @param {string} req.params.p1 - The tile size or static parameter, if available
* @param {string} req.params.p2 - The z, static, or raw parameter.
* @param {string} req.params.p3 - The x or staticType parameter.
* @param {string} req.params.p4 - The y or width parameter.
* @param {string} req.params.p5 - The height parameter.
* @param {string} req.params.scale - The scale parameter.
* @param {string} req.params.format - The format of the image.
* @returns {Promise<void>}
*/
app.get(
`/:id{/:p1}/:p2/:p3/:p4{x:p5}{@:scale}{.:format}`,
async (req, res, next) => {
try {
const { p2 } = req.params;
if (p2 === 'static') {
if (verbose) {
console.log(req.params);
}
const { p1, p2 } = req.params;
if ((!p1 && p2 === 'static') || (p1 === 'static' && p2 === 'raw')) {
// Route to static if p2 is static
if (options.serveStaticMaps !== false) {
return handleStaticRequest(
@ -920,6 +943,14 @@ export const serve_rendered = {
},
);
/**
* Handles requests for tile json endpoint.
* @param {object} req - Express request object.
* @param {object} res - Express response object.
* @param {string} req.params.id - The id of the tilejson
* @param {string} [req.params.tileSize] - The size of the tile, if specified.
* @returns {void}
*/
app.get('{/:tileSize}/:id.json', (req, res, next) => {
const item = repo[req.params.id];
if (!item) {

View file

@ -29,9 +29,11 @@ export const serve_style = {
* Initializes the serve_style module.
* @param {object} options Configuration options.
* @param {object} repo Repository object.
* @param {object} programOpts - An object containing the program options.
* @returns {express.Application} The initialized Express application.
*/
init: function (options, repo) {
init: function (options, repo, programOpts) {
const { verbose } = programOpts;
const app = express().disable('x-powered-by');
app.get('/:id/style.json', (req, res, next) => {
@ -64,6 +66,9 @@ export const serve_style = {
});
app.get(`/:id/sprite{/:spriteID}{@:scale}{.:format}`, (req, res, next) => {
if (verbose) {
console.log(req.params);
}
const { spriteID = 'default', id, format } = req.params;
const spriteScale = allowedSpriteScales(req.params.scale);

View file

@ -76,7 +76,7 @@ async function start(opts) {
config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
} catch (e) {
console.log('ERROR: Config file not found or invalid!');
console.log(' See README.md for instructions and sample data.');
console.log(' See README.md for instructions and sample data.');
process.exit(1);
}
}
@ -167,12 +167,12 @@ async function start(opts) {
app.use(cors());
}
app.use('/data/', serve_data.init(options, serving.data));
app.use('/data/', serve_data.init(options, serving.data, opts));
app.use('/files/', express.static(paths.files));
app.use('/styles/', serve_style.init(options, serving.styles));
app.use('/styles/', serve_style.init(options, serving.styles, opts));
if (!isLight) {
startupPromises.push(
serve_rendered.init(options, serving.rendered).then((sub) => {
serve_rendered.init(options, serving.rendered, opts).then((sub) => {
app.use('/styles/', sub);
}),
);
@ -288,7 +288,7 @@ async function start(opts) {
addStyle(id, item, true, true);
}
startupPromises.push(
serve_font(options, serving.fonts).then((sub) => {
serve_font(options, serving.fonts, opts).then((sub) => {
app.use('/', sub);
}),
);
@ -342,6 +342,13 @@ async function start(opts) {
}
});
}
/**
* Handles requests for a list of available styles.
* @param {object} req - Express request object.
* @param {object} res - Express response object.
* @param {string} [req.query.key] - Optional API key.
* @returns {void}
*/
app.get('/styles.json', (req, res, next) => {
const result = [];
const query = req.query.key
@ -395,15 +402,35 @@ async function start(opts) {
return arr;
}
/**
* Handles requests for a rendered tilejson endpoint.
* @param {object} req - Express request object.
* @param {object} res - Express response object.
* @param {string} req.params.tileSize - Optional tile size parameter.
* @returns {void}
*/
app.get('{/:tileSize}/rendered.json', (req, res, next) => {
const tileSize = allowedTileSizes(req.params['tileSize']);
res.send(addTileJSONs([], req, 'rendered', parseInt(tileSize, 10)));
});
/**
* Handles requests for a data tilejson endpoint.
* @param {object} req - Express request object.
* @param {object} res - Express response object.
* @returns {void}
*/
app.get('/data.json', (req, res) => {
res.send(addTileJSONs([], req, 'data', undefined));
});
/**
* Handles requests for a combined rendered and data tilejson endpoint.
* @param {object} req - Express request object.
* @param {object} res - Express response object.
* @param {string} req.params.tileSize - Optional tile size parameter.
* @returns {void}
*/
app.get('{/:tileSize}/index.json', (req, res, next) => {
const tileSize = allowedTileSizes(req.params['tileSize']);
res.send(
@ -421,6 +448,7 @@ async function start(opts) {
app.use('/', express.static(path.join(__dirname, '../public/resources')));
const templates = path.join(__dirname, '../public/templates');
/**
* Serves a Handlebars template.
* @param {string} urlPath - The URL path to serve the template at
@ -477,6 +505,12 @@ async function start(opts) {
}
}
/**
* Handles requests for the index page, providing a list of available styles and data.
* @param {object} req - Express request object.
* @param {object} res - Express response object.
* @returns {void}
*/
serveTemplate('/', 'index', (req) => {
let styles = {};
for (const id of Object.keys(serving.styles || {})) {
@ -489,11 +523,15 @@ async function start(opts) {
if (style.serving_rendered) {
const { center } = style.serving_rendered.tileJSON;
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]);
// Set thumbnail default size to be 256px x 256px
style.thumbnail = `${Math.floor(center[2])}/${Math.floor(centerPx[0] / 256)}/${Math.floor(centerPx[1] / 256)}.png`;
style.thumbnail = `${Math.floor(center[2])}/${Math.floor(
centerPx[0] / 256,
)}/${Math.floor(centerPx[1] / 256)}.png`;
}
const tileSize = 512;
@ -549,7 +587,9 @@ async function start(opts) {
}
if (center) {
const centerPx = mercator.px([center[0], center[1]], center[2]);
data.thumbnail = `${Math.floor(center[2])}/${Math.floor(centerPx[0] / 256)}/${Math.floor(centerPx[1] / 256)}.${tileJSON.format}`;
data.thumbnail = `${Math.floor(center[2])}/${Math.floor(
centerPx[0] / 256,
)}/${Math.floor(centerPx[1] / 256)}.${tileJSON.format}`;
}
}
@ -574,6 +614,13 @@ async function start(opts) {
};
});
/**
* Handles requests for a map viewer template for a specific style.
* @param {object} req - Express request object.
* @param {object} res - Express response object.
* @param {string} req.params.id - ID of the style.
* @returns {void}
*/
serveTemplate('/styles/:id/', 'viewer', (req) => {
const { id } = req.params;
const style = clone(((serving.styles || {})[id] || {}).styleJSON);
@ -590,6 +637,13 @@ async function start(opts) {
};
});
/**
* Handles requests for a Web Map Tile Service (WMTS) XML template.
* @param {object} req - Express request object.
* @param {object} res - Express response object.
* @param {string} req.params.id - ID of the style.
* @returns {void}
*/
serveTemplate('/styles/:id/wmts.xml', 'wmts', (req) => {
const { id } = req.params;
const wmts = clone((serving.styles || {})[id]);
@ -621,6 +675,14 @@ async function start(opts) {
};
});
/**
* Handles requests for a data view template for a specific data source.
* @param {object} req - Express request object.
* @param {object} res - Express response object.
* @param {string} req.params.id - ID of the data source.
* @param {string} [req.params.view] - Optional view type.
* @returns {void}
*/
serveTemplate('/data{/:view}/:id/', 'data', (req) => {
const { id, view } = req.params;
const data = serving.data[id];
@ -649,6 +711,12 @@ async function start(opts) {
startupComplete = true;
});
/**
* Handles requests to see the health of the server.
* @param {object} req - Express request object.
* @param {object} res - Express response object.
* @returns {void}
*/
app.get('/health', (req, res) => {
if (startupComplete) {
return res.status(200).send('OK');
@ -678,7 +746,6 @@ async function start(opts) {
startupPromise,
};
}
/**
* Stop the server gracefully
* @param {string} signal Name of the received signal