import tcircle from '@turf/circle'; import tunion from '@turf/union'; import ttrunc from '@turf/truncate'; const truncOptions = { precision: 6, coordinates: 2 }; // How many polygons to process before yielding to the event loop once. const YIELD_EVERY = 100; const yieldToEventLoop = () => new Promise((resolve) => { setImmediate(resolve); }); /** * Circle-union of subscriptions, equivalent to map-common-utils' calcUnion(typeCircle=true), * but split into a `for` loop that yields to the event loop every YIELD_EVERY polygons. * The vendored calcUnion runs the same turf.union chain fully synchronously, which for * thousands of subscriptions blocks Meteor's single-threaded DDP/HTTP handling for minutes * (see plan-modernizacion notes on the subsUnion hang). This keeps the exact same geometry * output while letting other requests interleave between chunks. */ const calcUnionAsync = async (subs, decorated) => { const unionGroup = []; for (let i = 0; i < subs.length; i += 1) { const osub = subs[i]; try { if (osub.location && osub.location.lat && osub.location.lon && osub.distance) { const dsub = decorated(osub); unionGroup.push(tcircle( [dsub.location.lon, dsub.location.lat], dsub.distance, { units: 'kilometers', steps: 144 } )); } else { console.info(`Wrong element to do union ${JSON.stringify(osub)}`); } } catch (e) { console.error(e, `Wrong element trying to make union ${JSON.stringify(osub)}`); } // Deliberate: yield to the event loop periodically instead of blocking it. // eslint-disable-next-line no-await-in-loop if (i % YIELD_EVERY === 0) await yieldToEventLoop(); } let unionTemp = unionGroup.length > 0 ? ttrunc(unionGroup[0], truncOptions) : null; for (let i = 1; i < unionGroup.length; i += 1) { unionTemp = ttrunc(tunion(unionTemp, unionGroup[i]), truncOptions); // Deliberate: yield to the event loop periodically instead of blocking it. // eslint-disable-next-line no-await-in-loop if (i % YIELD_EVERY === 0) await yieldToEventLoop(); } return unionTemp; }; export default calcUnionAsync;