diff --git a/src/healthcheck.js b/src/healthcheck.js index b3a0ab0..8f09dfd 100644 --- a/src/healthcheck.js +++ b/src/healthcheck.js @@ -1,9 +1,26 @@ import * as http from 'http'; + +/** + * Options for the HTTP request. + * @type {object} + * @property {number} timeout - Timeout for the request in milliseconds. + */ const options = { timeout: 2000, }; + +/** + * The URL to make the HTTP request to. + * @type {string} + */ const url = 'http://localhost:8080/health'; -const request = http.request(url, options, (res) => { + +/** + * Makes an HTTP request to the health endpoint and checks the response. + * Exits the process with a 0 status code if the health check is successful (status 200), + * or with a 1 status code otherwise. + */ +const request = http.request(url, options, function (res) { console.log(`STATUS: ${res.statusCode}`); if (res.statusCode == 200) { process.exit(0); @@ -11,8 +28,15 @@ const request = http.request(url, options, (res) => { process.exit(1); } }); + +/** + * Handles errors that occur during the HTTP request. + * Logs an error message and exits the process with a 1 status code. + * @param {Error} err - The error object. + */ request.on('error', function (err) { console.log('ERROR'); process.exit(1); }); + request.end(); diff --git a/src/main.js b/src/main.js index b1f14a2..774a207 100644 --- a/src/main.js +++ b/src/main.js @@ -70,6 +70,12 @@ const opts = program.opts(); console.log(`Starting ${packageJson.name} v${packageJson.version}`); +/** + * Starts the tile server with the given configuration. + * @param {string|null} configPath - The path to the configuration file, or null if not using a config file. + * @param {object|null} config - The configuration object, or null if reading from a file. + * @returns {Promise} - A Promise that resolves when the server starts. + */ const startServer = (configPath, config) => { let publicUrl = opts.public_url; if (publicUrl && publicUrl.lastIndexOf('/') !== publicUrl.length - 1) { @@ -89,6 +95,12 @@ const startServer = (configPath, config) => { }); }; +/** + * Starts the server with a given input file (MBTiles or PMTiles). + * Automatically creates a basic config file based on the input file. + * @param {string} inputFile - The path to the input MBTiles or PMTiles file. + * @returns {Promise} - A Promise that resolves when the server starts. + */ const startWithInputFile = async (inputFile) => { console.log(`[INFO] Automatically creating config file for ${inputFile}`); console.log(`[INFO] Only a basic preview style will be used.`); @@ -242,6 +254,14 @@ const startWithInputFile = async (inputFile) => { } }; +/** + * Main function to start the server. Checks for a config file or input file, + * and starts the server based on the available inputs. + * If no config or input file are provided, downloads a demo file. + * @async + * @returns {Promise} - A Promise that resolves when the server starts or finishes the download. + */ +// eslint-disable-next-line security/detect-non-literal-fs-filename fs.stat(path.resolve(opts.config), async (err, stats) => { if (err || !stats.isFile() || stats.size === 0) { let inputFile; diff --git a/src/mbtiles_wrapper.js b/src/mbtiles_wrapper.js index 95d121c..1d1ac9d 100644 --- a/src/mbtiles_wrapper.js +++ b/src/mbtiles_wrapper.js @@ -2,7 +2,8 @@ import MBTiles from '@mapbox/mbtiles'; import util from 'node:util'; /** - * Promise-ful wrapper around the MBTiles class. + * A promise-based wrapper around the `@mapbox/mbtiles` class, + * providing asynchronous access to MBTiles database functionality. */ class MBTilesWrapper { constructor(mbtiles) { @@ -11,27 +12,30 @@ class MBTilesWrapper { } /** - * Get the underlying MBTiles object. - * @returns {MBTiles} + * Gets the underlying MBTiles object. + * @returns {MBTiles} The underlying MBTiles object. */ getMbTiles() { return this._mbtiles; } /** - * Get the MBTiles metadata object. - * @returns {Promise} + * Gets the MBTiles metadata object. + * @async + * @returns {Promise} A promise that resolves with the MBTiles metadata. */ - getInfo() { + async getInfo() { return this._getInfoP(); } } /** - * Open the given MBTiles file and return a promise that resolves with a - * MBTilesWrapper instance. - * @param inputFile Input file - * @returns {Promise} + * Opens an MBTiles file and returns a promise that resolves with an MBTilesWrapper instance. + * + * The MBTiles database is opened in read-only mode. + * @param {string} inputFile - The path to the MBTiles file. + * @returns {Promise} A promise that resolves with a new MBTilesWrapper instance. + * @throws {Error} If there is an error opening the MBTiles file. */ export function openMbTilesWrapper(inputFile) { return new Promise((resolve, reject) => { diff --git a/src/pmtiles_adapter.js b/src/pmtiles_adapter.js index 3987681..64ff385 100644 --- a/src/pmtiles_adapter.js +++ b/src/pmtiles_adapter.js @@ -2,13 +2,37 @@ import fs from 'node:fs'; import { PMTiles, FetchSource } from 'pmtiles'; import { isValidHttpUrl } from './utils.js'; +/** + * A PMTiles source that reads data from a file descriptor. + */ class PMTilesFileSource { + /** + * Creates a new PMTilesFileSource instance. + * @param {number} fd - The file descriptor of the PMTiles file. + */ constructor(fd) { + /** + * @type {number} The file descriptor of the PMTiles file + * @private + */ this.fd = fd; } + + /** + * Returns the file descriptor. + * @returns {number} The file descriptor. + */ getKey() { return this.fd; } + + /** + * Asynchronously retrieves a chunk of bytes from the PMTiles file. + * @async + * @param {number} offset - The byte offset to start reading from. + * @param {number} length - The number of bytes to read. + * @returns {Promise<{data: ArrayBuffer}>} A promise that resolves with an object containing the read bytes as an ArrayBuffer. + */ async getBytes(offset, length) { const buffer = Buffer.alloc(length); await readFileBytes(this.fd, buffer, offset); @@ -21,10 +45,13 @@ class PMTilesFileSource { } /** - * - * @param fd - * @param buffer - * @param offset + * Asynchronously reads a specified number of bytes from a file descriptor into a buffer. + * @async + * @param {number} fd - The file descriptor to read from. + * @param {Buffer} buffer - The buffer to write the read bytes into. + * @param {number} offset - The byte offset in the file to start reading from. + * @returns {Promise} A promise that resolves when the read operation completes. + * @throws {Error} If there is an error during the read operation. */ async function readFileBytes(fd, buffer, offset) { return new Promise((resolve, reject) => { @@ -38,8 +65,9 @@ async function readFileBytes(fd, buffer, offset) { } /** - * - * @param FilePath + * Opens a PMTiles file (either local or remote) and returns a PMTiles instance. + * @param {string} FilePath - The path to the PMTiles file or a URL. + * @returns {PMTiles} A PMTiles instance. */ export function openPMtiles(FilePath) { let pmtiles = undefined; @@ -56,8 +84,10 @@ export function openPMtiles(FilePath) { } /** - * - * @param pmtiles + * Asynchronously retrieves metadata and header information from a PMTiles file. + * @async + * @param {PMTiles} pmtiles - The PMTiles instance. + * @returns {Promise} A promise that resolves with the metadata object. */ export async function getPMtilesInfo(pmtiles) { const header = await pmtiles.getHeader(); @@ -97,11 +127,13 @@ export async function getPMtilesInfo(pmtiles) { } /** - * - * @param pmtiles - * @param z - * @param x - * @param y + * Asynchronously retrieves a tile from a PMTiles file. + * @async + * @param {PMTiles} pmtiles - The PMTiles instance. + * @param {number} z - The zoom level of the tile. + * @param {number} x - The x coordinate of the tile. + * @param {number} y - The y coordinate of the tile. + * @returns {Promise<{data: Buffer|undefined, header: object}>} A promise that resolves with an object containing the tile data (as a Buffer, or undefined if not found) and the appropriate headers. */ export async function getPMtilesTile(pmtiles, z, x, y) { const header = await pmtiles.getHeader(); @@ -116,8 +148,9 @@ export async function getPMtilesTile(pmtiles, z, x, y) { } /** - * - * @param typenum + * Determines the tile type and corresponding headers based on the PMTiles tile type number. + * @param {number} typenum - The tile type number from the PMTiles header. + * @returns {{type: string, header: object}} An object containing the tile type and associated headers. */ function getPmtilesTileType(typenum) { let head = {}; diff --git a/src/render.js b/src/render.js index a7489f7..f7376f4 100644 --- a/src/render.js +++ b/src/render.js @@ -1,34 +1,47 @@ 'use strict'; import { createCanvas, Image } from 'canvas'; - import SphericalMercator from '@mapbox/sphericalmercator'; const mercator = new SphericalMercator(); /** - * Transforms coordinates to pixels. - * @param {List[Number]} ll Longitude/Latitude coordinate pair. - * @param {number} zoom Map zoom level. + * Transforms geographical coordinates (longitude/latitude) to pixel coordinates at a given zoom level. + * Uses spherical mercator projection and calculates pixel coordinates relative to zoom level 20 then scales it. + * @param {number[]} ll - Longitude/Latitude coordinate pair [longitude, latitude]. + * @param {number} zoom - Map zoom level. + * @returns {number[]} Pixel coordinates [x, y]. */ -const precisePx = (ll, zoom) => { +function precisePx(ll, zoom) { const px = mercator.px(ll, 20); const scale = Math.pow(2, zoom - 20); return [px[0] * scale, px[1] * scale]; -}; +} /** - * Draws a marker in canvas context. - * @param {object} ctx Canvas context object. - * @param {object} marker Marker object parsed by extractMarkersFromQuery. - * @param {number} z Map zoom level. + * Draws a marker on a canvas context. + * The marker image is loaded asynchronously. + * @async + * @param {CanvasRenderingContext2D} ctx - Canvas context object. + * @param {object} marker - Marker object, with properties like `icon`, `location`, `offsetX`, `offsetY`, `scale`. + * @param {number} z - Map zoom level. + * @returns {Promise} A promise that resolves when the marker image is loaded and drawn. + * @throws {Error} If there is an error loading the marker image. */ -const drawMarker = (ctx, marker, z) => { +function drawMarker(ctx, marker, z) { return new Promise((resolve) => { const img = new Image(); const pixelCoords = precisePx(marker.location, z); - const getMarkerCoordinates = (imageWidth, imageHeight, scale) => { + /** + * Calculates the pixel coordinates for placing the marker image on the canvas. + * Takes into account the image dimensions, scaling, and any offsets. + * @param {number} imageWidth - The width of the marker image. + * @param {number} imageHeight - The height of the marker image. + * @param {number} scale - The scaling factor. + * @returns {{x: number, y: number}} An object containing the x and y pixel coordinates. + */ + function getMarkerCoordinates(imageWidth, imageHeight, scale) { // Images are placed with their top-left corner at the provided location // within the canvas but we expect icons to be centered and above it. @@ -53,9 +66,12 @@ const drawMarker = (ctx, marker, z) => { x: xCoordinate, y: yCoordinate, }; - }; + } - const drawOnCanvas = () => { + /** + * + */ + function drawOnCanvas() { // Check if the images should be resized before beeing drawn const defaultScale = 1; const scale = marker.scale ? marker.scale : defaultScale; @@ -75,7 +91,7 @@ const drawMarker = (ctx, marker, z) => { } // Resolve the promise when image has been drawn resolve(); - }; + } img.onload = drawOnCanvas; img.onerror = (err) => { @@ -83,18 +99,20 @@ const drawMarker = (ctx, marker, z) => { }; img.src = marker.icon; }); -}; +} /** * Draws a list of markers onto a canvas. * Wraps drawing of markers into list of promises and awaits them. - * It's required because images are expected to load asynchronous in canvas js + * It's required because images are expected to load asynchronously in canvas js * even when provided from a local disk. - * @param {object} ctx Canvas context object. - * @param {List[Object]} markers Marker objects parsed by extractMarkersFromQuery. - * @param {number} z Map zoom level. + * @async + * @param {CanvasRenderingContext2D} ctx - Canvas context object. + * @param {object[]} markers - Array of marker objects, see drawMarker for individual marker properties. + * @param {number} z - Map zoom level. + * @returns {Promise} A promise that resolves when all marker images are loaded and drawn. */ -const drawMarkers = async (ctx, markers, z) => { +async function drawMarkers(ctx, markers, z) { const markerPromises = []; for (const marker of markers) { @@ -104,17 +122,18 @@ const drawMarkers = async (ctx, markers, z) => { // Await marker drawings before continuing await Promise.all(markerPromises); -}; +} /** - * Draws a list of coordinates onto a canvas and styles the resulting path. - * @param {object} ctx Canvas context object. - * @param {List[Number]} path List of coordinates. - * @param {object} query Request query parameters. - * @param {string} pathQuery Path query parameter. - * @param {number} z Map zoom level. + * Draws a path (polyline or polygon) on a canvas with specified styles. + * @param {CanvasRenderingContext2D} ctx - Canvas context object. + * @param {number[][]} path - List of coordinates [longitude, latitude] representing the path. + * @param {object} query - Request query parameters, which can include `fill`, `width`, `borderwidth`, `linecap`, `linejoin`, `border`, and `stroke` styles. + * @param {string} pathQuery - Path specific query parameters, which can include `fill:`, `width:`, `stroke:` styles. + * @param {number} z - Map zoom level. + * @returns {void | null} */ -const drawPath = (ctx, path, query, pathQuery, z) => { +function drawPath(ctx, path, query, pathQuery, z) { const splitPaths = pathQuery.split('|'); if (!path || path.length < 2) { @@ -209,9 +228,26 @@ const drawPath = (ctx, path, query, pathQuery, z) => { ctx.strokeStyle = 'rgba(0,64,255,0.7)'; } ctx.stroke(); -}; + return; +} -export const renderOverlay = async ( +/** + * Renders an overlay on a canvas, including paths and markers. + * @async + * @param {number} z - Map zoom level. + * @param {number} x - X tile coordinate. + * @param {number} y - Y tile coordinate. + * @param {number} bearing - Map bearing in degrees. + * @param {number} pitch - Map pitch in degrees. + * @param {number} w - Width of the canvas. + * @param {number} h - Height of the canvas. + * @param {number} scale - Scaling factor. + * @param {number[][][]} paths - Array of paths, each path is an array of coordinate pairs [longitude, latitude]. + * @param {object[]} markers - Array of marker objects, see drawMarker for individual marker properties. + * @param {object} query - Request query parameters. + * @returns {Promise} A promise that resolves with the canvas as a Buffer or null if nothing to draw. + */ +export async function renderOverlay( z, x, y, @@ -223,7 +259,7 @@ export const renderOverlay = async ( paths, markers, query, -) => { +) { if ((!paths || paths.length === 0) && (!markers || markers.length === 0)) { return null; } @@ -261,9 +297,17 @@ export const renderOverlay = async ( await drawMarkers(ctx, markers, z); return canvas.toBuffer(); -}; +} -export const renderWatermark = (width, height, scale, text) => { +/** + * Renders a watermark text on a canvas. + * @param {number} width - Width of the canvas. + * @param {number} height - Height of the canvas. + * @param {number} scale - Scaling factor. + * @param {string} text - The watermark text to render. + * @returns {HTMLCanvasElement} A canvas element with the rendered watermark text. + */ +export function renderWatermark(width, height, scale, text) { const canvas = createCanvas(scale * width, scale * height); const ctx = canvas.getContext('2d'); ctx.scale(scale, scale); @@ -276,9 +320,17 @@ export const renderWatermark = (width, height, scale, text) => { ctx.fillText(text, 5, height - 5); return canvas; -}; +} -export const renderAttribution = (width, height, scale, text) => { +/** + * Renders an attribution text on a canvas with a background. + * @param {number} width - Width of the canvas. + * @param {number} height - Height of the canvas. + * @param {number} scale - Scaling factor. + * @param {string} text - The attribution text to render. + * @returns {HTMLCanvasElement} A canvas element with the rendered attribution text. + */ +export function renderAttribution(width, height, scale, text) { const canvas = createCanvas(scale * width, scale * height); const ctx = canvas.getContext('2d'); ctx.scale(scale, scale); @@ -300,4 +352,4 @@ export const renderAttribution = (width, height, scale, text) => { ctx.fillText(text, width - textWidth - padding / 2, height - textHeight + 8); return canvas; -}; +}