// Plain CommonJS worker_threads entry point, NOT part of the Meteor imports // graph (lives under private/ so `meteor build` copies it verbatim into // programs/server/assets/app/workers/unionWorker.js instead of compiling it). // // Runs the CPU-heavy part of the subscriptions geo-union (circle generation + // sequential turf.union chain) off the main thread. A single tunion() call // against an already-complex merged polygon can itself take seconds once // there are thousands of subscriptions — yielding between iterations on the // main thread doesn't help when one iteration alone blocks that long, so this // needs a real worker thread, not just cooperative scheduling. // // workerData.subs must already be validated (location.lat/lon/distance // present) and decorated (addNoisy/noNoisy already applied) by the caller. // workerData.baseUnion, if given, is an already-computed union GeoJSON that // subs gets merged into (the incremental-add fast path) instead of starting // from scratch — for a single new subscription this is one tunion() call // instead of redoing the whole chain over every subscription again. // workerData.turfPaths gives absolute paths to @turf/circle, @turf/union and // @turf/truncate — this file lives under programs/server/assets/, while the // app's own npm deps live in the sibling programs/server/npm/node_modules, // which plain require('@turf/circle') can never find by walking up parent // directories. The caller resolves the paths (it's Meteor-compiled code that // knows how to find them) and hands them over instead. const { parentPort, workerData } = require('worker_threads'); // eslint-disable-next-line import/no-dynamic-require const tcircle = require(workerData.turfPaths.circle).default; // eslint-disable-next-line import/no-dynamic-require const tunion = require(workerData.turfPaths.union).default; // eslint-disable-next-line import/no-dynamic-require const ttrunc = require(workerData.turfPaths.truncate).default; const truncOptions = { precision: 6, coordinates: 2 }; const run = () => { const { subs, baseUnion } = workerData; const circles = subs.map(s => tcircle( [s.location.lon, s.location.lat], s.distance, { units: 'kilometers', steps: 144 } )); let unionTemp = baseUnion || null; let start = 0; if (!unionTemp && circles.length > 0) { unionTemp = ttrunc(circles[0], truncOptions); start = 1; } for (let i = start; i < circles.length; i += 1) { unionTemp = ttrunc(tunion(unionTemp, circles[i]), truncOptions); } return unionTemp; }; try { parentPort.postMessage({ union: run() }); } catch (e) { parentPort.postMessage({ error: e.message }); }