fix: grey line cut off at the edge of the world copy

The map repeats the world sideways, and zoomed out several copies are on screen
at once. The rings span −180…+180 once, so the shading ended in a hard vertical
edge wherever the next copy began: it read as a rectangle laid over the planet
rather than as night.

Each ring is now drawn again shifted a full turn either way. Three copies cover
every zoom this map reaches and cost nothing — three more polygons on a canvas
layer.

Zooming the map to fit one world would have hidden it rather than fixed it, and
would have taken away the free pan and zoom the operator has.
This commit is contained in:
2026-07-30 19:35:26 +02:00
parent 3b58e39eec
commit f82631690a
2 changed files with 33 additions and 13 deletions
+29 -11
View File
@@ -190,22 +190,40 @@ export function WorldMap({ fromGrid, toGrid, fromLabel, toLabel, beamAzimuths, b
greylineLayer.current?.remove();
const g = L.layerGroup([], { pane: 'greyline' });
// The rings span 180…+180 once, but this map repeats the world sideways
// (worldCopyJump, and zoomed out several copies are on screen at once). A
// single ring therefore ends in a hard vertical edge wherever a copy
// begins — the shading looked like a rectangle laid over the planet.
//
// So each ring is drawn again shifted a full turn either way. Three copies
// cover every zoom level this map reaches; they cost nothing, being three
// polygons on a canvas layer.
const COPIES = [-360, 0, 360];
const shifted = (ring: [number, number][], by: number): [number, number][] =>
by === 0 ? ring : ring.map(([lat, lon]) => [lat, lon + by] as [number, number]);
// Two bands, drawn dark-to-light so they read as one gradient into night:
// full darkness (Sun below the horizon) and civil twilight (down to 6°),
// which is the band operators actually mean by "grey line".
const night = L.polygon(nightPolygon(now, 0), {
pane: 'greyline', stroke: false, fillColor: '#0b1220', fillOpacity: 0.32, interactive: false,
});
const twilight = L.polygon(nightPolygon(now, -6), {
pane: 'greyline', stroke: false, fillColor: '#0b1220', fillOpacity: 0.22, interactive: false,
});
const nightRing = nightPolygon(now, 0);
const twilightRing = nightPolygon(now, -6);
// The terminator itself, so the line is readable at a glance rather than
// being inferred from where the shading fades.
const line = L.polyline(nightPolygon(now, 0).slice(0, -2), {
pane: 'greyline', color: '#f59e0b', weight: 1, opacity: 0.75, interactive: false,
});
// being inferred from where the shading fades. Dropping the last two
// points leaves the terminator alone, without the segment that closes the
// polygon along the dark pole.
const lineRing = nightRing.slice(0, -2);
night.addTo(g); twilight.addTo(g); line.addTo(g);
for (const by of COPIES) {
L.polygon(shifted(twilightRing, by), {
pane: 'greyline', stroke: false, fillColor: '#0b1220', fillOpacity: 0.22, interactive: false,
}).addTo(g);
L.polygon(shifted(nightRing, by), {
pane: 'greyline', stroke: false, fillColor: '#0b1220', fillOpacity: 0.32, interactive: false,
}).addTo(g);
L.polyline(shifted(lineRing, by), {
pane: 'greyline', color: '#f59e0b', weight: 1, opacity: 0.75, interactive: false,
}).addTo(g);
}
g.addTo(m);
greylineLayer.current = g;
};