import fs from 'node:fs'; import { PMTiles, FetchSource } from 'pmtiles'; import { isValidHttpUrl } from './utils.js'; class PMTilesFileSource { constructor(fd) { this.fd = fd; } getKey() { return this.fd; } async getBytes(offset, length) { const buffer = Buffer.alloc(length); await readFileBytes(this.fd, buffer, offset); const ab = buffer.buffer.slice( buffer.byteOffset, buffer.byteOffset + buffer.byteLength, ); return { data: ab }; } } /** * * @param fd * @param buffer * @param offset */ async function readFileBytes(fd, buffer, offset) { return new Promise((resolve, reject) => { fs.read(fd, buffer, 0, buffer.length, offset, (err) => { if (err) { return reject(err); } resolve(); }); }); } /** * * @param FilePath */ export function openPMtiles(FilePath) { let pmtiles = undefined; if (isValidHttpUrl(FilePath)) { const source = new FetchSource(FilePath); pmtiles = new PMTiles(source); } else { const fd = fs.openSync(FilePath, 'r'); const source = new PMTilesFileSource(fd); pmtiles = new PMTiles(source); } return pmtiles; } /** * * @param pmtiles */ export async function getPMtilesInfo(pmtiles) { const header = await pmtiles.getHeader(); const metadata = await pmtiles.getMetadata(); //Add missing metadata from header metadata['format'] = getPmtilesTileType(header.tileType).type; metadata['minzoom'] = header.minZoom; metadata['maxzoom'] = header.maxZoom; if (header.minLon && header.minLat && header.maxLon && header.maxLat) { metadata['bounds'] = [ header.minLon, header.minLat, header.maxLon, header.maxLat, ]; } else { metadata['bounds'] = [-180, -85.05112877980659, 180, 85.0511287798066]; } if (header.centerZoom) { metadata['center'] = [ header.centerLon, header.centerLat, header.centerZoom, ]; } else { metadata['center'] = [ header.centerLon, header.centerLat, parseInt(metadata['maxzoom']) / 2, ]; } return metadata; } /** * * @param pmtiles * @param z * @param x * @param y */ export async function getPMtilesTile(pmtiles, z, x, y) { const header = await pmtiles.getHeader(); const tileType = getPmtilesTileType(header.tileType); let zxyTile = await pmtiles.getZxy(z, x, y); if (zxyTile && zxyTile.data) { zxyTile = Buffer.from(zxyTile.data); } else { zxyTile = undefined; } return { data: zxyTile, header: tileType.header }; } /** * * @param typenum */ function getPmtilesTileType(typenum) { let head = {}; let tileType; switch (typenum) { case 0: tileType = 'Unknown'; break; case 1: tileType = 'pbf'; head['Content-Type'] = 'application/x-protobuf'; break; case 2: tileType = 'png'; head['Content-Type'] = 'image/png'; break; case 3: tileType = 'jpeg'; head['Content-Type'] = 'image/jpeg'; break; case 4: tileType = 'webp'; head['Content-Type'] = 'image/webp'; break; case 5: tileType = 'avif'; head['Content-Type'] = 'image/avif'; break; } return { type: tileType, header: head }; }