fix(perimeters): handle antimeridian queries and incident card anchors

Rebase #1142 onto current main, keeping both sides of the changelog and runtime documentation conflicts. Retain unwrapped containment and circular query coordinates.

Address review feedback on perimeterAnchorDegrees: use circular longitude for the selected outer ring, excluding its closing duplicate. Compare multipart areas along short longitude edges so a small dateline part cannot steal the card anchor from a larger polygon.

Three new card regressions fail before the fix and pass afterward. Coverage includes reversed/rotated rings, vertices at +/-180 and both multipart selection directions. Local validation: 43 focused tests; full suite 7,636 passed, one skipped, zero failures; production build, formatting, module boundaries and whitespace checks pass on Node 24.19.0. Browser tracking validation follows on the QA branch.
This commit is contained in:
Arianna Method committed 2026-10-16 21:33:01 +03:00
1 parent e8b3a10a07
commit 8d37661219
8 files changed
+300 -18

No files matched your search

+21 -4
View File
@@ -323,13 +323,30 @@ export function lineTouchesArea(coords, area) {
/** Whether a point lies inside a ring of `[lon, lat]` vertices (ray casting). */
function ringContains(ring, point) {
if (ring.length < 3) return false;
// Keep adjacent vertices on the same longitude sheet, as the edge tests do.
// Wrapping each vertex around the query point would split a dateline ring
// when queried from Greenwich and mistake its complement for its interior.
let previous = ring[0][0];
let west = previous;
let east = previous;
const vertices = ring.map(([lon, lat]) => {
if (Math.abs(lon - previous) > 180)
lon += 360 * Math.round((previous - lon) / 360);
previous = lon;
west = Math.min(west, lon);
east = Math.max(east, lon);
return [lon, lat];
});
const middle = (west + east) / 2;
const lon = point.lon + 360 * Math.round((middle - point.lon) / 360);
let inside = false;
for (let i = 0, j = ring.length - 1; i < ring.length; j = i, i += 1) {
const [xi, yi] = ring[i];
const [xj, yj] = ring[j];
for (let i = 0, j = vertices.length - 1; i < vertices.length; j = i, i += 1) {
const [xi, yi] = vertices[i];
const [xj, yj] = vertices[j];
if (
yi > point.lat !== yj > point.lat &&
point.lon < ((xj - xi) * (point.lat - yi)) / (yj - yi) + xi
lon < ((xj - xi) * (point.lat - yi)) / (yj - yi) + xi
)
inside = !inside;
}