diff --git a/src/serve_data.js b/src/serve_data.js index 7d37f90..a4379ff 100644 --- a/src/serve_data.js +++ b/src/serve_data.js @@ -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} + */ 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} + */ 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} + */ 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} */ add: async function (options, repo, params, id, programOpts) { diff --git a/src/serve_font.js b/src/serve_font.js index 30f1fc8..49e6a41 100644 --- a/src/serve_font.js +++ b/src/serve_font.js @@ -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} - 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} + */ 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( diff --git a/src/serve_light.js b/src/serve_light.js index 474a781..7e49c49 100644 --- a/src/serve_light.js +++ b/src/serve_light.js @@ -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) => {}, }; diff --git a/src/serve_rendered.js b/src/serve_rendered.js index 1f298b1..3413ca9 100644 --- a/src/serve_rendered.js +++ b/src/serve_rendered.js @@ -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} @@ -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} 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} + */ 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) { diff --git a/src/serve_style.js b/src/serve_style.js index 5a74782..b5f59aa 100644 --- a/src/serve_style.js +++ b/src/serve_style.js @@ -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); diff --git a/src/server.js b/src/server.js index beee4b3..524d1ae 100644 --- a/src/server.js +++ b/src/server.js @@ -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