diff --git a/src/compile/native-road.js b/src/compile/native-road.js index f508bec..65f4b4a 100644 --- a/src/compile/native-road.js +++ b/src/compile/native-road.js @@ -1621,19 +1621,55 @@ function directionArrowFeatures(road, lane, controlFeatures, diagnostics) { return features; } +// Control rings are the same array objects across the whole compile, while the +// candidate rings are built fresh, so only the controls are worth caching. Their +// bounds used to be recomputed inside every comparison: the callers walk each lane +// in 0.25 m steps and test the step's rectangle against every control, which on a +// 41-road area is millions of calls. Recomputing four `Math.min(...ring.map(...))` +// spreads per call made `bounds` alone 821 ms of a 1575 ms compile, plus 163 ms of +// GC from the intermediate arrays. +const controlRingBounds = new WeakMap(); + +function ringBounds(ring) { + let minX = Infinity; + let minY = Infinity; + let maxX = -Infinity; + let maxY = -Infinity; + for (let index = 0; index < ring.length; index += 1) { + const x = ring[index][0]; + const y = ring[index][1]; + if (x < minX) minX = x; + if (x > maxX) maxX = x; + if (y < minY) minY = y; + if (y > maxY) maxY = y; + } + return [minX, minY, maxX, maxY]; +} + +function controlBounds(ring) { + let box = controlRingBounds.get(ring); + if (!box) { + box = ringBounds(ring); + controlRingBounds.set(ring, box); + } + return box; +} + +function boundsDisjoint(a, b) { + return a[0] > b[2] || a[2] < b[0] || a[1] > b[3] || a[3] < b[1]; +} + function ringsOverlapControl(rings, controls) { - return rings.some((ring) => controls.some((feature) => ringsOverlap(ring, feature.geometry.coordinates[0]))); + return rings.some((ring) => { + const box = ringBounds(ring); + return controls.some((feature) => { + const other = feature.geometry.coordinates[0]; + // Cheap rejection first; the exact test only runs for boxes that touch. + return !boundsDisjoint(box, controlBounds(other)) && ringsOverlap(ring, other); + }); + }); } function ringsOverlap(first, second) { - const bounds = (ring) => [ - Math.min(...ring.map((point) => point[0])), - Math.min(...ring.map((point) => point[1])), - Math.max(...ring.map((point) => point[0])), - Math.max(...ring.map((point) => point[1])), - ]; - const a = bounds(first); - const b = bounds(second); - if (a[0] > b[2] || a[2] < b[0] || a[1] > b[3] || a[3] < b[1]) return false; if (first.some((point) => pointInPolygon(point, second)) || second.some((point) => pointInPolygon(point, first))) return true; return first