add more missing headers / cleanup

This commit is contained in:
acalcutt 2025-01-11 01:59:32 -05:00 committed by Andrew Calcutt
parent f2b48acb61
commit 5f40c26349
5 changed files with 196 additions and 63 deletions

View file

@ -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();

View file

@ -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<void>} - 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<void>} - 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<void>} - 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;

View file

@ -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<object>}
* Gets the MBTiles metadata object.
* @async
* @returns {Promise<object>} 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<MBTilesWrapper>}
* 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<MBTilesWrapper>} 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) => {

View file

@ -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<void>} 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<object>} 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 = {};

View file

@ -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<void>} 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<void>} 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<Buffer | null>} 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;
};
}