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, isValidHttpUrl,
fetchTileData, fetchTileData,
} from './utils.js'; } from './utils.js';
import { import { getPMtilesInfo, openPMtiles } from './pmtiles_adapter.js';
getPMtilesInfo,
getPMtilesTile,
openPMtiles,
} from './pmtiles_adapter.js';
import { gunzipP, gzipP } from './promises.js'; import { gunzipP, gzipP } from './promises.js';
import { openMbTilesWrapper } from './mbtiles_wrapper.js'; import { openMbTilesWrapper } from './mbtiles_wrapper.js';
@ -30,12 +26,28 @@ export const serve_data = {
* Initializes the serve_data module. * Initializes the serve_data module.
* @param {object} options Configuration options. * @param {object} options Configuration options.
* @param {object} repo Repository object. * @param {object} repo Repository object.
* @param {object} programOpts - An object containing the program options
* @returns {express.Application} The initialized Express application. * @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'); 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) => { app.get('/:id/:z/:x/:y.:format', async (req, res) => {
if (verbose) {
console.log(req.params);
}
const item = repo[req.params.id]; const item = repo[req.params.id];
if (!item) { if (!item) {
return res.sendStatus(404); return res.sendStatus(404);
@ -88,7 +100,14 @@ export const serve_data = {
data = await gunzipP(data); data = await gunzipP(data);
isGzipped = false; 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); data = JSON.stringify(geojson);
} }
delete headers['ETag']; // do not trust the tile ETag -- regenerate if (headers) {
delete headers['ETag'];
}
headers['Content-Encoding'] = 'gzip'; headers['Content-Encoding'] = 'gzip';
res.set(headers); res.set(headers);
@ -123,6 +144,16 @@ export const serve_data = {
return res.status(200).send(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) => { app.get('/:id/elevation/:z/:x/:y', async (req, res, next) => {
try { try {
const item = repo?.[req.params.id]; const item = repo?.[req.params.id];
@ -189,6 +220,7 @@ export const serve_data = {
const { minX, minY } = new SphericalMercator().xyz(tileCenter, z); const { minX, minY } = new SphericalMercator().xyz(tileCenter, z);
xy = [minX, minY]; xy = [minX, minY];
} }
const fetchTile = await fetchTileData(source, sourceType, z, x, y); const fetchTile = await fetchTileData(source, sourceType, z, x, y);
if (fetchTile == null) return res.status(204).send(); 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) => { app.get('/:id.json', (req, res) => {
const item = repo[req.params.id]; const item = repo[req.params.id];
if (!item) { if (!item) {
@ -289,6 +328,9 @@ export const serve_data = {
* @param {object} params Parameters object. * @param {object} params Parameters object.
* @param {string} id ID of the data source. * @param {string} id ID of the data source.
* @param {object} programOpts - An object containing the program options * @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>} * @returns {Promise<void>}
*/ */
add: async function (options, repo, params, id, programOpts) { 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. * Initializes and returns an Express app that serves font files.
* @param {object} options - Configuration options for the server. * @param {object} options - Configuration options for the server.
* @param {object} allowedFonts - An object containing allowed fonts. * @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. * @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 app = express().disable('x-powered-by');
const lastModified = new Date().toUTCString(); const lastModified = new Date().toUTCString();
@ -19,7 +21,18 @@ export async function serve_font(options, allowedFonts) {
const existingFonts = {}; 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) => { app.get('/fonts/:fontstack/:range.pbf', async (req, res) => {
if (verbose) {
console.log(req.params);
}
const fontstack = decodeURI(req.params.fontstack); const fontstack = decodeURI(req.params.fontstack);
const range = req.params.range; 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) => { app.get('/fonts.json', (req, res) => {
res.header('Content-type', 'application/json'); res.header('Content-type', 'application/json');
return res.send( return res.send(

View file

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

View file

@ -267,7 +267,6 @@ function extractPathsFromQuery(query, transformer) {
} }
return paths; return paths;
} }
/** /**
* Parses marker options provided via query and sets corresponding attributes * Parses marker options provided via query and sets corresponding attributes
* on marker object. * on marker object.
@ -626,6 +625,13 @@ const respondImage = async (
* @param {object} options - Configuration options for the server. * @param {object} options - Configuration options for the server.
* @param {object} repo - The repository object holding style data. * @param {object} repo - The repository object holding style data.
* @param {object} req - Express request object. * @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 {object} res - Express response object.
* @param {Function} next - Express next middleware function. * @param {Function} next - Express next middleware function.
* @param {number} maxScaleFactor - The maximum scale factor allowed. * @param {number} maxScaleFactor - The maximum scale factor allowed.
@ -641,12 +647,12 @@ async function handleTileRequest(
) { ) {
const { const {
id, id,
p1: tileSize,
p2: zParam, p2: zParam,
p3: xParam, p3: xParam,
p4: yParam, p4: yParam,
scale: scaleParam, scale: scaleParam,
format, format,
p1: tileSize,
} = req.params; } = req.params;
const item = repo[id]; const item = repo[id];
if (!item) { if (!item) {
@ -694,6 +700,12 @@ async function handleTileRequest(
* @param {object} repo - The repository object holding style data. * @param {object} repo - The repository object holding style data.
* @param {object} req - Express request object. * @param {object} req - Express request object.
* @param {object} res - Express response 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 {Function} next - Express next middleware function.
* @param {number} maxScaleFactor - The maximum scale factor allowed. * @param {number} maxScaleFactor - The maximum scale factor allowed.
* @returns {Promise<void>} * @returns {Promise<void>}
@ -708,32 +720,24 @@ async function handleStaticRequest(
) { ) {
const { const {
id, id,
scale: scaleParam,
format,
p2: raw, p2: raw,
p3: type, p3: staticType,
p4: width, p4: width,
p5: height, p5: height,
scale: scaleParam,
format,
} = req.params; } = req.params;
const item = repo[id]; const item = repo[id];
const parsedWidth = parseInt(width) || 512; const parsedWidth = parseInt(width) || 512;
const parsedHeight = parseInt(height) || 512; const parsedHeight = parseInt(height) || 512;
const scale = parseScale(scaleParam, maxScaleFactor); const scale = parseScale(scaleParam, maxScaleFactor);
let isRaw = raw !== undefined; let isRaw = raw === 'raw';
let staticType = type;
if (!staticType) {
//workaround for type when raw is not set
staticType = raw;
isRaw = false;
}
if (!item || !staticType || !format || !scale) { if (!item || !staticType || !format || !scale) {
return res.sendStatus(404); return res.sendStatus(404);
} }
const staticTypeMatch = staticType.match(staticTypeRegex); const staticTypeMatch = staticType.match(staticTypeRegex);
console.log(staticTypeMatch);
if (staticTypeMatch.groups.lon) { if (staticTypeMatch.groups.lon) {
// Center Based Static Image // Center Based Static Image
const z = parseFloat(staticTypeMatch.groups.zoom) || 0; const z = parseFloat(staticTypeMatch.groups.zoom) || 0;
@ -879,18 +883,37 @@ export const serve_rendered = {
* Initializes the serve_rendered module. * Initializes the serve_rendered module.
* @param {object} options Configuration options. * @param {object} options Configuration options.
* @param {object} repo Repository object. * @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. * @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); maxScaleFactor = Math.min(Math.floor(options.maxScaleFactor || 3), 9);
const app = express().disable('x-powered-by'); 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( app.get(
`/:id{/:p1}/:p2/:p3/:p4{x:p5}{@:scale}{.:format}`, `/:id{/:p1}/:p2/:p3/:p4{x:p5}{@:scale}{.:format}`,
async (req, res, next) => { async (req, res, next) => {
try { try {
const { p2 } = req.params; if (verbose) {
if (p2 === 'static') { console.log(req.params);
}
const { p1, p2 } = req.params;
if ((!p1 && p2 === 'static') || (p1 === 'static' && p2 === 'raw')) {
// Route to static if p2 is static // Route to static if p2 is static
if (options.serveStaticMaps !== false) { if (options.serveStaticMaps !== false) {
return handleStaticRequest( 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) => { app.get('{/:tileSize}/:id.json', (req, res, next) => {
const item = repo[req.params.id]; const item = repo[req.params.id];
if (!item) { if (!item) {

View file

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

View file

@ -167,12 +167,12 @@ async function start(opts) {
app.use(cors()); 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('/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) { if (!isLight) {
startupPromises.push( startupPromises.push(
serve_rendered.init(options, serving.rendered).then((sub) => { serve_rendered.init(options, serving.rendered, opts).then((sub) => {
app.use('/styles/', sub); app.use('/styles/', sub);
}), }),
); );
@ -288,7 +288,7 @@ async function start(opts) {
addStyle(id, item, true, true); addStyle(id, item, true, true);
} }
startupPromises.push( startupPromises.push(
serve_font(options, serving.fonts).then((sub) => { serve_font(options, serving.fonts, opts).then((sub) => {
app.use('/', 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) => { app.get('/styles.json', (req, res, next) => {
const result = []; const result = [];
const query = req.query.key const query = req.query.key
@ -395,15 +402,35 @@ async function start(opts) {
return arr; 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) => { app.get('{/:tileSize}/rendered.json', (req, res, next) => {
const tileSize = allowedTileSizes(req.params['tileSize']); const tileSize = allowedTileSizes(req.params['tileSize']);
res.send(addTileJSONs([], req, 'rendered', parseInt(tileSize, 10))); 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) => { app.get('/data.json', (req, res) => {
res.send(addTileJSONs([], req, 'data', undefined)); 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) => { app.get('{/:tileSize}/index.json', (req, res, next) => {
const tileSize = allowedTileSizes(req.params['tileSize']); const tileSize = allowedTileSizes(req.params['tileSize']);
res.send( res.send(
@ -421,6 +448,7 @@ async function start(opts) {
app.use('/', express.static(path.join(__dirname, '../public/resources'))); app.use('/', express.static(path.join(__dirname, '../public/resources')));
const templates = path.join(__dirname, '../public/templates'); const templates = path.join(__dirname, '../public/templates');
/** /**
* Serves a Handlebars template. * Serves a Handlebars template.
* @param {string} urlPath - The URL path to serve the template at * @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) => { serveTemplate('/', 'index', (req) => {
let styles = {}; let styles = {};
for (const id of Object.keys(serving.styles || {})) { for (const id of Object.keys(serving.styles || {})) {
@ -489,11 +523,15 @@ async function start(opts) {
if (style.serving_rendered) { if (style.serving_rendered) {
const { center } = style.serving_rendered.tileJSON; const { center } = style.serving_rendered.tileJSON;
if (center) { if (center) {
style.viewer_hash = `#${center[2]}/${center[1].toFixed(5)}/${center[0].toFixed(5)}`; style.viewer_hash = `#${center[2]}/${center[1].toFixed(
5,
)}/${center[0].toFixed(5)}`;
const centerPx = mercator.px([center[0], center[1]], center[2]); const centerPx = mercator.px([center[0], center[1]], center[2]);
// Set thumbnail default size to be 256px x 256px // 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; const tileSize = 512;
@ -549,7 +587,9 @@ async function start(opts) {
} }
if (center) { if (center) {
const centerPx = mercator.px([center[0], center[1]], center[2]); const centerPx = mercator.px([center[0], center[1]], center[2]);
data.thumbnail = `${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) => { serveTemplate('/styles/:id/', 'viewer', (req) => {
const { id } = req.params; const { id } = req.params;
const style = clone(((serving.styles || {})[id] || {}).styleJSON); 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) => { serveTemplate('/styles/:id/wmts.xml', 'wmts', (req) => {
const { id } = req.params; const { id } = req.params;
const wmts = clone((serving.styles || {})[id]); 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) => { serveTemplate('/data{/:view}/:id/', 'data', (req) => {
const { id, view } = req.params; const { id, view } = req.params;
const data = serving.data[id]; const data = serving.data[id];
@ -649,6 +711,12 @@ async function start(opts) {
startupComplete = true; 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) => { app.get('/health', (req, res) => {
if (startupComplete) { if (startupComplete) {
return res.status(200).send('OK'); return res.status(200).send('OK');
@ -678,7 +746,6 @@ async function start(opts) {
startupPromise, startupPromise,
}; };
} }
/** /**
* Stop the server gracefully * Stop the server gracefully
* @param {string} signal Name of the received signal * @param {string} signal Name of the received signal