UNPKG

js-subpcd

Version:

🌟 High-performance JavaScript/TypeScript QuadTree point cloud filtering and processing library. Published on npm as js-subpcd with PCL.js compatible API for spatial filtering, subsampling, and nearest neighbor search.

277 lines (242 loc) • 8.06 kB
/** * QuadTree Worker for Node.js using worker_threads * Handles heavy QuadTree operations in a separate thread */ import { parentPort, workerData } from 'worker_threads'; import { QuadTreeFilter, PointXYZ, Point3D } from './index'; // Worker message types interface WorkerMessage { type: 'BUILD_TREE' | 'FILTER_BY_DEPTH' | 'FILTER_BY_GRID_SPACING' | 'ABORT'; data?: any; } interface WorkerResponse { type: 'TREE_BUILT' | 'POINTS_FILTERED' | 'ERROR' | 'PROGRESS'; data?: any; error?: string; points?: Point3D[]; processingTime?: number; depth?: number; gridSize?: number; expectedMax?: number; bounds?: number[]; pointCount?: number; message?: string; progress?: number; } // Worker state let quadTreeFilter: QuadTreeFilter | null = null; let isBuilt: boolean = false; let isAborted: boolean = false; /** * Handle incoming messages from main thread */ function handleMessage(message: WorkerMessage): void { if (!parentPort) return; try { switch (message.type) { case 'BUILD_TREE': handleBuildTree(message.data); break; case 'FILTER_BY_DEPTH': handleFilterByDepth(message.data); break; case 'FILTER_BY_GRID_SPACING': handleFilterByGridSpacing(message.data); break; case 'ABORT': handleAbort(); break; default: throw new Error(`Unknown message type: ${message.type}`); } } catch (error: any) { const response: WorkerResponse = { type: 'ERROR', error: error.message || 'Unknown worker error' }; parentPort.postMessage(response); } } /** * Handle tree building */ function handleBuildTree(data: { points: Array<{x: number, y: number, z: number}>; bounds: number[]; minCellSize: number; }): void { if (!parentPort) return; try { console.log(`Worker: Building QuadTree for ${data.points.length} points`); const startTime = performance.now(); // Reset abort flag isAborted = false; // Create QuadTree filter quadTreeFilter = new QuadTreeFilter(data.minCellSize); // Convert points to PointXYZ objects const points: PointXYZ[] = data.points.map(p => new PointXYZ(p.x, p.y, p.z)); // Set points directly quadTreeFilter.dataLoader.points = points; // Build the tree const coords = quadTreeFilter.dataLoader.getCoordinateArrays(); quadTreeFilter.quadtreeManager.buildTreeFromArrays( coords.x, coords.y, coords.z, data.bounds ); const buildTime = performance.now() - startTime; isBuilt = true; if (!isAborted) { const response: WorkerResponse = { type: 'TREE_BUILT', data: { pointCount: data.points.length, bounds: data.bounds, buildTime: buildTime }, processingTime: buildTime, pointCount: data.points.length, bounds: data.bounds }; parentPort.postMessage(response); } } catch (error: any) { const response: WorkerResponse = { type: 'ERROR', error: `Failed to build tree: ${error.message}` }; parentPort.postMessage(response); } } /** * Handle filter by depth */ async function handleFilterByDepth(data: { targetDepth: number; }): Promise<void> { if (!parentPort) return; try { if (!quadTreeFilter || !isBuilt) { throw new Error('QuadTree not built'); } console.log(`Worker: Filtering by depth ${data.targetDepth}`); const startTime = performance.now(); // Reset abort flag isAborted = false; // Perform filtering const filteredPoints = await quadTreeFilter.filterByDepth(data.targetDepth); const filterTime = performance.now() - startTime; if (!isAborted) { const gridSize = Math.pow(2, data.targetDepth); const response: WorkerResponse = { type: 'POINTS_FILTERED', points: filteredPoints, processingTime: filterTime, depth: data.targetDepth, gridSize: gridSize, expectedMax: gridSize * gridSize, message: `Filtered to ${filteredPoints.length} points using ${gridSize}x${gridSize} grid` }; parentPort.postMessage(response); } } catch (error: any) { const response: WorkerResponse = { type: 'ERROR', error: `Failed to filter by depth: ${error.message}` }; parentPort.postMessage(response); } } /** * Handle filter by grid spacing */ async function handleFilterByGridSpacing(data: { xCoords: number[]; yCoords: number[]; zCoords: number[]; minLateralSpacing: number; }): Promise<void> { if (!parentPort) return; try { if (!quadTreeFilter) { throw new Error('QuadTree filter not initialized'); } console.log(`Worker: Filtering by grid spacing ${data.minLateralSpacing}`); const startTime = performance.now(); // Reset abort flag isAborted = false; // Perform filtering const filteredPoints = quadTreeFilter.filterPointsByGridSpacing( data.xCoords, data.yCoords, data.zCoords, data.minLateralSpacing ); const filterTime = performance.now() - startTime; if (!isAborted) { const response: WorkerResponse = { type: 'POINTS_FILTERED', points: filteredPoints, processingTime: filterTime, message: `Filtered to ${filteredPoints.length} points with ${data.minLateralSpacing}m spacing` }; parentPort.postMessage(response); } } catch (error: any) { const response: WorkerResponse = { type: 'ERROR', error: `Failed to filter by grid spacing: ${error.message}` }; parentPort.postMessage(response); } } /** * Handle abort signal */ function handleAbort(): void { console.log('Worker: Received abort signal'); isAborted = true; quadTreeFilter = null; isBuilt = false; } /** * Send progress updates */ function sendProgress(progress: number, message: string): void { if (!parentPort || isAborted) return; const response: WorkerResponse = { type: 'PROGRESS', progress: progress, message: message }; parentPort.postMessage(response); } // Set up message handler if (parentPort) { parentPort.on('message', handleMessage); // Send ready signal parentPort.postMessage({ type: 'READY', message: 'QuadTree worker ready' }); } else { console.error('Worker: parentPort not available'); } // Handle uncaught exceptions process.on('uncaughtException', (error: Error) => { if (parentPort) { parentPort.postMessage({ type: 'ERROR', error: `Uncaught exception: ${error.message}` }); } process.exit(1); }); // Handle unhandled rejections process.on('unhandledRejection', (reason: any) => { if (parentPort) { parentPort.postMessage({ type: 'ERROR', error: `Unhandled rejection: ${reason}` }); } process.exit(1); });