Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions test/antimeridian.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { GreatCircle } from '../src';
import type { MultiLineString, LineString } from 'geojson';

// Routes that cross the antimeridian
const PACIFIC_ROUTES = [
{ name: 'Tokyo → LAX', start: { x: 139.7798, y: 35.5494 }, end: { x: -118.4085, y: 33.9416 } },
{ name: 'Auckland → LAX', start: { x: 174.79, y: -36.85 }, end: { x: -118.41, y: 33.94 } },
{ name: 'Shanghai → SFO', start: { x: 121.81, y: 31.14 }, end: { x: -122.38, y: 37.62 } },
];

function assertSplitAtAntimeridian(coords: number[][][]) {
const seg0 = coords[0];
const seg1 = coords[1];

expect(seg0).toBeDefined();
expect(seg1).toBeDefined();

if (!seg0 || !seg1) return; // narrow for TS

const lastOfFirst = seg0[seg0.length - 1];
const firstOfSecond = seg1[0];

expect(lastOfFirst).toBeDefined();
expect(firstOfSecond).toBeDefined();

if (!lastOfFirst || !firstOfSecond) return; // narrow for TS

// Both sides of the split must be at ±180
expect(Math.abs(lastOfFirst[0] ?? NaN)).toBeCloseTo(180, 1);
expect(Math.abs(firstOfSecond[0] ?? NaN)).toBeCloseTo(180, 1);

// Latitudes must match — no gap at the antimeridian
expect(lastOfFirst[1] ?? NaN).toBeCloseTo(firstOfSecond[1] ?? NaN, 3);
}
Comment on lines +11 to +34
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

assertSplitAtAntimeridian only checks abs(lon) ≈ 180, so it would still pass if both segment endpoints use the same sign (e.g. 180/180) or if the geometry contains >2 segments with later bad splits. To prevent false positives, assert coords.length === 2, both segments are non-empty, and that the split longitudes are exactly 180 and -180 (opposite signs) for the adjacent endpoints (matching the implementation and existing tests in test/great-circle.test.ts).

Copilot uses AI. Check for mistakes.

describe('antimeridian splitting', () => {
describe('with npoints=100', () => {
for (const { name, start, end } of PACIFIC_ROUTES) {
test(`${name} produces a split MultiLineString`, () => {
const result = new GreatCircle(start, end).Arc(100, { offset: 10 }).json();
expect(result.geometry.type).toBe('MultiLineString');
assertSplitAtAntimeridian((result.geometry as MultiLineString).coordinates);
});
}
});

Comment on lines +37 to +46
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file duplicates the existing Tokyo/Auckland/Shanghai antimeridian split assertions already present in test/great-circle.test.ts for npoints=100. Consider keeping the new low-npoints coverage but avoid repeating the npoints=100 suite (or move these cases into the existing "Dateline crossing" block) to reduce duplicated fixtures and maintenance overhead.

Suggested change
describe('with npoints=100', () => {
for (const { name, start, end } of PACIFIC_ROUTES) {
test(`${name} produces a split MultiLineString`, () => {
const result = new GreatCircle(start, end).Arc(100, { offset: 10 }).json();
expect(result.geometry.type).toBe('MultiLineString');
assertSplitAtAntimeridian((result.geometry as MultiLineString).coordinates);
});
}
});

Copilot uses AI. Check for mistakes.
describe('with npoints=10', () => {
for (const { name, start, end } of PACIFIC_ROUTES) {
test(`${name} splits correctly`, () => {
const result = new GreatCircle(start, end).Arc(10, { offset: 10 }).json();
expect(result.geometry.type).toBe('MultiLineString');
assertSplitAtAntimeridian((result.geometry as MultiLineString).coordinates);
});
Comment on lines +49 to +53
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As written, the npoints=10 tests will fail on the current GreatCircle.Arc implementation (it often returns an unsplit LineString for Tokyo→LAX and Shanghai→SFO because the split detector only triggers for longitude diffs > 360 - offset). If this PR is intended to be tests-only, mark the known-bad cases with test.failing/test.todo (or skip with a link to #75) to keep CI green; otherwise include the corresponding implementation fix in this PR.

Suggested change
test(`${name} splits correctly`, () => {
const result = new GreatCircle(start, end).Arc(10, { offset: 10 }).json();
expect(result.geometry.type).toBe('MultiLineString');
assertSplitAtAntimeridian((result.geometry as MultiLineString).coordinates);
});
if (name === 'Tokyo → LAX' || name === 'Shanghai → SFO') {
test.skip(`${name} splits correctly (known issue, see #75)`, () => {
const result = new GreatCircle(start, end).Arc(10, { offset: 10 }).json();
expect(result.geometry.type).toBe('MultiLineString');
assertSplitAtAntimeridian((result.geometry as MultiLineString).coordinates);
});
} else {
test(`${name} splits correctly`, () => {
const result = new GreatCircle(start, end).Arc(10, { offset: 10 }).json();
expect(result.geometry.type).toBe('MultiLineString');
assertSplitAtAntimeridian((result.geometry as MultiLineString).coordinates);
});
}

Copilot uses AI. Check for mistakes.
}
});

describe('non-crossing routes are unaffected', () => {
test('Seattle → DC returns a LineString with no longitude jumps', () => {
const result = new GreatCircle({ x: -122, y: 48 }, { x: -77, y: 39 }).Arc(100, { offset: 10 }).json();
expect(result.geometry.type).toBe('LineString');

const coords = (result.geometry as LineString).coordinates;
for (let i = 1; i < coords.length; i++) {
const prev = coords[i - 1];
const curr = coords[i];
if (!prev || !curr) continue;
expect(Math.abs((curr[0] ?? 0) - (prev[0] ?? 0))).toBeLessThan(20);
}
});
});
});
Loading