fix(maps): the ground track stops cutting straight across the map
splitAtAntimeridian works from which COPY of the world each longitude is in, so it needs a series that runs past ±180 rather than jumping back. greatCirclePoints produces one; a satellite ground track does not — SGP4 reports every longitude inside (−180, 180]. A track leaving Kamchatka at +179.9 and arriving in Alaska at −179.9 therefore looked like a single 359.8° step inside one world, no split was made, and the polyline drew the chord: a straight dashed line clean across the map on every crossing. The series is now unwrapped first. A jump of more than 180° between consecutive points is that wrap and nothing else — no real path steps half the globe between samples — and the unwrap is idempotent, so the great-circle caller is unaffected. Checked on three series: a normalised crossing now yields two segments meeting exactly at ±180 where it used to yield one chord; an already continuous 170→183 still splits at 180; a track that never crosses comes back untouched.
This commit is contained in:
@@ -186,8 +186,38 @@ export function greatCirclePoints(
|
|||||||
// opposite edge, at the SAME latitude, so the line leaves one side of the map
|
// opposite edge, at the SAME latitude, so the line leaves one side of the map
|
||||||
// and re-enters the other at the height it left. Leaflet takes the result as a
|
// and re-enters the other at the height it left. Leaflet takes the result as a
|
||||||
// multi-polyline, so one path is still one layer.
|
// multi-polyline, so one path is still one layer.
|
||||||
export function splitAtAntimeridian(pts: [number, number][]): [number, number][][] {
|
// unwrapLon makes a longitude series continuous.
|
||||||
if (pts.length === 0) return [];
|
//
|
||||||
|
// splitAtAntimeridian works from which COPY of the world each longitude is in,
|
||||||
|
// which requires the series to run past ±180 rather than jumping back. A great
|
||||||
|
// circle built by greatCirclePoints already does; a satellite ground track does
|
||||||
|
// not — SGP4 reports every longitude inside (−180, 180], so a track leaving
|
||||||
|
// Kamchatka at +179.9 and arriving in Alaska at −179.9 looked to the splitter
|
||||||
|
// like one step of 359.8° inside a single world. No split was made and the
|
||||||
|
// polyline drew the chord: a straight dashed line clean across the map, from
|
||||||
|
// one side of the planet to the other, on every crossing.
|
||||||
|
//
|
||||||
|
// A jump of more than 180° between two consecutive points is that wrap and
|
||||||
|
// nothing else: no real path steps half the globe between samples. Idempotent
|
||||||
|
// on a series that was already continuous, so both callers can share it.
|
||||||
|
function unwrapLon(pts: [number, number][]): [number, number][] {
|
||||||
|
if (pts.length < 2) return pts;
|
||||||
|
const out: [number, number][] = [pts[0]];
|
||||||
|
let turns = 0;
|
||||||
|
for (let i = 1; i < pts.length; i++) {
|
||||||
|
const [lat, lon] = pts[i];
|
||||||
|
const prevRaw = pts[i - 1][1];
|
||||||
|
const d = lon - prevRaw;
|
||||||
|
if (d > 180) turns -= 1;
|
||||||
|
else if (d < -180) turns += 1;
|
||||||
|
out.push([lat, lon + 360 * turns]);
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function splitAtAntimeridian(input: [number, number][]): [number, number][][] {
|
||||||
|
if (input.length === 0) return [];
|
||||||
|
const pts = unwrapLon(input);
|
||||||
// Which copy of the world a longitude belongs to: 0 is the map's own.
|
// Which copy of the world a longitude belongs to: 0 is the map's own.
|
||||||
const world = (lon: number) => Math.floor((lon + 180) / 360);
|
const world = (lon: number) => Math.floor((lon + 180) / 360);
|
||||||
const norm = (lon: number) => lon - 360 * world(lon);
|
const norm = (lon: number) => lon - 360 * world(lon);
|
||||||
|
|||||||
Reference in New Issue
Block a user