diff --git a/src/data/trafficBounds.js b/src/data/trafficBounds.js index 9f303785e..aed0053b7 100644 --- a/src/data/trafficBounds.js +++ b/src/data/trafficBounds.js @@ -129,17 +129,6 @@ function longitudeSpanDeg(west, east) { return raw >= 0 ? raw : raw + 360; } -/** - * Normalize a longitude into [-180, 180). Matches the wrap idiom already - * used by `destinationPoint()` above. - * - * @param {number} lon - Longitude in degrees. - * @returns {number} Longitude normalized to [-180, 180). - */ -function normalizeLonDeg(lon) { - return ((lon + 540) % 360) - 180; -} - /** * Clamp a bounding box's spans to `maxSpanDeg` and recenter it on `center`. * @@ -147,9 +136,12 @@ function normalizeLonDeg(lon) { * but centers the box on the derived look-at point instead of the view * rectangle's midpoint. Idempotent when `center` is the box's own midpoint. * - * Longitude is handled as a cyclic axis: the input span wraps correctly - * across the antimeridian, and the output west/east are normalized back into - * [-180, 180) instead of drifting outside the normal coordinate range. + * Longitude is handled as a cyclic axis for the span calculation, so the + * input span wraps correctly across the antimeridian. The output west/east + * are deliberately left unnormalized (may fall outside [-180, 180)) so that + * `west <= east` always holds — callers such as `getBoundsCenter` and + * `boundsOverlap` rely on that invariant and would silently misbehave on a + * normalized-but-inverted box (see #392 review). * * @param {{south:number, west:number, north:number, east:number}} bounds * Source bounds (span donor). @@ -163,7 +155,7 @@ export function clampBoundsAroundCenter(bounds, center, maxSpanDeg = 0.05) { return { south: center.lat - latSpan / 2, north: center.lat + latSpan / 2, - west: normalizeLonDeg(center.lon - lonSpan / 2), - east: normalizeLonDeg(center.lon + lonSpan / 2), + west: center.lon - lonSpan / 2, + east: center.lon + lonSpan / 2, }; } diff --git a/src/data/trafficBounds.test.mjs b/src/data/trafficBounds.test.mjs index 63450dbb9..005123ddd 100644 --- a/src/data/trafficBounds.test.mjs +++ b/src/data/trafficBounds.test.mjs @@ -124,14 +124,15 @@ test('span clamp handles bounds crossing the antimeridian', () => { const result = clampBoundsAroundCenter(bounds, center, 0.05); - assert.ok(result.west >= -180 && result.west < 180); - assert.ok(result.east >= -180 && result.east < 180); + // west/east are deliberately left unnormalized (may exceed 180) so that + // west <= east always holds for downstream consumers (getBoundsCenter, + // boundsOverlap) that assume a monotonic box. See #392 review. + assert.ok(result.west <= result.east, `west/east not monotonic: ${result.west}, ${result.east}`); + assert.ok(Math.abs(result.east - result.west - 0.04) < 1e-12); - // Wrapped longitude span should remain about 0.04 degrees. - const span = - result.east >= result.west - ? result.east - result.west - : result.east - result.west + 360; - - assert.ok(Math.abs(span - 0.04) < 1e-12); + // The plain-mean center a caller like getBoundsCenter computes must land + // back on the antimeridian near the requested center, not ~180 degrees + // away on the opposite side of the planet. + const center2 = (result.west + result.east) / 2; + assert.ok(Math.abs(center2 - center.lon) < 1e-9, `center drifted: ${center2}`); });