From 74aae70af2fa995945cf89402147d52f7f03d6a2 Mon Sep 17 00:00:00 2001 From: accius Date: Mon, 2 Feb 2026 18:38:36 -0500 Subject: [PATCH 1/7] stab at fixing antimeridian issues --- src/components/WorldMap.jsx | 26 ++++++++++++++++++-------- src/utils/geo.js | 17 ++++++++++++++--- 2 files changed, 32 insertions(+), 11 deletions(-) diff --git a/src/components/WorldMap.jsx b/src/components/WorldMap.jsx index b7043e6..0f0cf63 100644 --- a/src/components/WorldMap.jsx +++ b/src/components/WorldMap.jsx @@ -538,14 +538,24 @@ export const WorldMap = ({ ); // Validate points before creating polyline - if (points && points.length > 1 && points.every(p => Array.isArray(p) && !isNaN(p[0]) && !isNaN(p[1]))) { - const line = L.polyline(points, { - color: bandColor, - weight: 1.5, - opacity: 0.5, - dashArray: '4, 4' - }).addTo(map); - pskMarkersRef.current.push(line); + // getGreatCirclePoints returns array of segments (each segment is array of [lat,lon]) + if (points && Array.isArray(points) && points.length > 0) { + // Check if it's segmented (array of arrays of points) or flat (single segment) + const isSegmented = Array.isArray(points[0]) && points[0].length > 0 && Array.isArray(points[0][0]); + const segments = isSegmented ? points : [points]; + + segments.forEach(segment => { + if (segment && Array.isArray(segment) && segment.length > 1 && + segment.every(p => Array.isArray(p) && !isNaN(p[0]) && !isNaN(p[1]))) { + const line = L.polyline(segment, { + color: bandColor, + weight: 1.5, + opacity: 0.5, + dashArray: '4, 4' + }).addTo(map); + pskMarkersRef.current.push(line); + } + }); } // Add small dot marker at spot location diff --git a/src/utils/geo.js b/src/utils/geo.js index 86e4f01..5b46e7d 100644 --- a/src/utils/geo.js +++ b/src/utils/geo.js @@ -210,15 +210,26 @@ export const getGreatCirclePoints = (lat1, lon1, lat2, lon2, n = 100) => { let currentSegment = [rawPoints[0]]; for (let i = 1; i < rawPoints.length; i++) { + const prevLat = rawPoints[i-1][0]; const prevLon = rawPoints[i-1][1]; + const currLat = rawPoints[i][0]; const currLon = rawPoints[i][1]; // Check if we crossed the antimeridian (lon jumps more than 180°) if (Math.abs(currLon - prevLon) > 180) { - // Finish current segment + // Interpolate the crossing point + // Normalize longitudes so the crossing is smooth + const prevLonAdj = prevLon; + const currLonAdj = currLon > prevLon ? currLon - 360 : currLon + 360; + const frac = (prevLon > 0 ? 180 - prevLon : -180 - prevLon) / (currLonAdj - prevLonAdj); + const crossLat = prevLat + frac * (currLat - prevLat); + + // End current segment at the map edge + currentSegment.push([crossLat, prevLon > 0 ? 180 : -180]); segments.push(currentSegment); - // Start new segment - currentSegment = []; + + // Start new segment from the opposite edge + currentSegment = [[crossLat, prevLon > 0 ? -180 : 180]]; } currentSegment.push(rawPoints[i]); } From 0aae5f317780559631188ade7cc423288a11ed54 Mon Sep 17 00:00:00 2001 From: accius Date: Mon, 2 Feb 2026 18:43:28 -0500 Subject: [PATCH 2/7] lolngitude unwrapping --- src/components/WorldMap.jsx | 92 +++++++++++++------------------------ src/utils/geo.js | 33 ++----------- 2 files changed, 37 insertions(+), 88 deletions(-) diff --git a/src/components/WorldMap.jsx b/src/components/WorldMap.jsx index 0f0cf63..ccec3b9 100644 --- a/src/components/WorldMap.jsx +++ b/src/components/WorldMap.jsx @@ -267,21 +267,16 @@ export const WorldMap = ({ const isHovered = hoveredSpot && hoveredSpot.call === path.dxCall && Math.abs(parseFloat(hoveredSpot.freq) - parseFloat(path.freq)) < 0.01; - // Handle segments - const isSegmented = Array.isArray(pathPoints[0]) && pathPoints[0].length > 0 && Array.isArray(pathPoints[0][0]); - const segments = isSegmented ? pathPoints : [pathPoints]; - - segments.forEach(segment => { - if (segment && Array.isArray(segment) && segment.length > 1) { - const line = L.polyline(segment, { - color: isHovered ? '#ffffff' : color, - weight: isHovered ? 4 : 1.5, - opacity: isHovered ? 1 : 0.5 - }).addTo(map); - if (isHovered) line.bringToFront(); - dxPathsLinesRef.current.push(line); - } - }); + // Handle path rendering (single continuous array, unwrapped across antimeridian) + if (pathPoints && Array.isArray(pathPoints) && pathPoints.length > 1) { + const line = L.polyline(pathPoints, { + color: isHovered ? '#ffffff' : color, + weight: isHovered ? 4 : 1.5, + opacity: isHovered ? 1 : 0.5 + }).addTo(map); + if (isHovered) line.bringToFront(); + dxPathsLinesRef.current.push(line); + } // Add DX marker const dxCircle = L.circleMarker([path.dxLat, path.dxLon], { @@ -358,34 +353,20 @@ export const WorldMap = ({ // Draw orbit track if available if (sat.track && sat.track.length > 1) { - // Split track into segments to handle date line crossing - let segments = []; - let currentSegment = [sat.track[0]]; - - for (let i = 1; i < sat.track.length; i++) { - const prevLon = sat.track[i-1][1]; - const currLon = sat.track[i][1]; - // If longitude jumps more than 180 degrees, start new segment - if (Math.abs(currLon - prevLon) > 180) { - segments.push(currentSegment); - currentSegment = []; - } - currentSegment.push(sat.track[i]); + // Unwrap longitudes for continuous rendering across antimeridian + const unwrapped = sat.track.map(p => [...p]); + for (let i = 1; i < unwrapped.length; i++) { + while (unwrapped[i][1] - unwrapped[i-1][1] > 180) unwrapped[i][1] -= 360; + while (unwrapped[i][1] - unwrapped[i-1][1] < -180) unwrapped[i][1] += 360; } - segments.push(currentSegment); - // Draw each segment - segments.forEach(segment => { - if (segment.length > 1) { - const trackLine = L.polyline(segment, { - color: sat.visible ? satColor : satColorDark, - weight: 2, - opacity: sat.visible ? 0.8 : 0.4, - dashArray: sat.visible ? null : '5, 5' - }).addTo(map); - satTracksRef.current.push(trackLine); - } - }); + const trackLine = L.polyline(unwrapped, { + color: sat.visible ? satColor : satColorDark, + weight: 2, + opacity: sat.visible ? 0.8 : 0.4, + dashArray: sat.visible ? null : '5, 5' + }).addTo(map); + satTracksRef.current.push(trackLine); } // Draw footprint circle if available and satellite is visible @@ -537,25 +518,16 @@ export const WorldMap = ({ 50 ); - // Validate points before creating polyline - // getGreatCirclePoints returns array of segments (each segment is array of [lat,lon]) - if (points && Array.isArray(points) && points.length > 0) { - // Check if it's segmented (array of arrays of points) or flat (single segment) - const isSegmented = Array.isArray(points[0]) && points[0].length > 0 && Array.isArray(points[0][0]); - const segments = isSegmented ? points : [points]; - - segments.forEach(segment => { - if (segment && Array.isArray(segment) && segment.length > 1 && - segment.every(p => Array.isArray(p) && !isNaN(p[0]) && !isNaN(p[1]))) { - const line = L.polyline(segment, { - color: bandColor, - weight: 1.5, - opacity: 0.5, - dashArray: '4, 4' - }).addTo(map); - pskMarkersRef.current.push(line); - } - }); + // Validate points before creating polyline (single continuous array, unwrapped across antimeridian) + if (points && Array.isArray(points) && points.length > 1 && + points.every(p => Array.isArray(p) && !isNaN(p[0]) && !isNaN(p[1]))) { + const line = L.polyline(points, { + color: bandColor, + weight: 1.5, + opacity: 0.5, + dashArray: '4, 4' + }).addTo(map); + pskMarkersRef.current.push(line); } // Add small dot marker at spot location diff --git a/src/utils/geo.js b/src/utils/geo.js index 5b46e7d..8ce24e8 100644 --- a/src/utils/geo.js +++ b/src/utils/geo.js @@ -205,37 +205,14 @@ export const getGreatCirclePoints = (lat1, lon1, lat2, lon2, n = 100) => { rawPoints.push([toDeg(Math.atan2(z, Math.sqrt(x*x+y*y))), toDeg(Math.atan2(y, x))]); } - // Split path at antimeridian crossings for proper Leaflet rendering - const segments = []; - let currentSegment = [rawPoints[0]]; - + // Unwrap longitudes to be continuous (no jumps > 180°) + // This lets Leaflet draw smoothly across the antimeridian and world copies for (let i = 1; i < rawPoints.length; i++) { - const prevLat = rawPoints[i-1][0]; - const prevLon = rawPoints[i-1][1]; - const currLat = rawPoints[i][0]; - const currLon = rawPoints[i][1]; - - // Check if we crossed the antimeridian (lon jumps more than 180°) - if (Math.abs(currLon - prevLon) > 180) { - // Interpolate the crossing point - // Normalize longitudes so the crossing is smooth - const prevLonAdj = prevLon; - const currLonAdj = currLon > prevLon ? currLon - 360 : currLon + 360; - const frac = (prevLon > 0 ? 180 - prevLon : -180 - prevLon) / (currLonAdj - prevLonAdj); - const crossLat = prevLat + frac * (currLat - prevLat); - - // End current segment at the map edge - currentSegment.push([crossLat, prevLon > 0 ? 180 : -180]); - segments.push(currentSegment); - - // Start new segment from the opposite edge - currentSegment = [[crossLat, prevLon > 0 ? -180 : 180]]; - } - currentSegment.push(rawPoints[i]); + while (rawPoints[i][1] - rawPoints[i-1][1] > 180) rawPoints[i][1] -= 360; + while (rawPoints[i][1] - rawPoints[i-1][1] < -180) rawPoints[i][1] += 360; } - segments.push(currentSegment); - return segments; + return rawPoints; }; export default { From a2f6a7998936083b874033431f5505f7a4a1b626 Mon Sep 17 00:00:00 2001 From: accius Date: Mon, 2 Feb 2026 18:47:16 -0500 Subject: [PATCH 3/7] end markers fixed --- src/components/WorldMap.jsx | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/components/WorldMap.jsx b/src/components/WorldMap.jsx index ccec3b9..70b163b 100644 --- a/src/components/WorldMap.jsx +++ b/src/components/WorldMap.jsx @@ -278,8 +278,13 @@ export const WorldMap = ({ dxPathsLinesRef.current.push(line); } + // Use unwrapped endpoint so marker sits where the line ends + const endPoint = pathPoints[pathPoints.length - 1]; + const dxLatDisplay = endPoint[0]; + const dxLonDisplay = endPoint[1]; + // Add DX marker - const dxCircle = L.circleMarker([path.dxLat, path.dxLon], { + const dxCircle = L.circleMarker([dxLatDisplay, dxLonDisplay], { radius: isHovered ? 10 : 6, fillColor: isHovered ? '#ffffff' : color, color: isHovered ? color : '#fff', @@ -300,7 +305,7 @@ export const WorldMap = ({ iconSize: null, iconAnchor: [0, 0] }); - const label = L.marker([path.dxLat, path.dxLon], { icon: labelIcon, interactive: false }).addTo(map); + const label = L.marker([dxLatDisplay, dxLonDisplay], { icon: labelIcon, interactive: false }).addTo(map); dxPathsMarkersRef.current.push(label); } } catch (err) { @@ -502,8 +507,8 @@ export const WorldMap = ({ if (showPSKReporter && pskReporterSpots && pskReporterSpots.length > 0 && hasValidDE) { pskReporterSpots.forEach(spot => { // Validate spot coordinates are valid numbers - const spotLat = parseFloat(spot.lat); - const spotLon = parseFloat(spot.lon); + let spotLat = parseFloat(spot.lat); + let spotLon = parseFloat(spot.lon); if (!isNaN(spotLat) && !isNaN(spotLon)) { const displayCall = spot.receiver || spot.sender; @@ -528,6 +533,11 @@ export const WorldMap = ({ dashArray: '4, 4' }).addTo(map); pskMarkersRef.current.push(line); + + // Use unwrapped endpoint so dot sits where the line ends + const endPoint = points[points.length - 1]; + spotLat = endPoint[0]; + spotLon = endPoint[1]; } // Add small dot marker at spot location From 729171a5cc11036b6d06c1403f8369e9eefe3056 Mon Sep 17 00:00:00 2001 From: accius Date: Mon, 2 Feb 2026 18:50:32 -0500 Subject: [PATCH 4/7] fix course zoom --- src/components/WorldMap.jsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/components/WorldMap.jsx b/src/components/WorldMap.jsx index 70b163b..a3bb823 100644 --- a/src/components/WorldMap.jsx +++ b/src/components/WorldMap.jsx @@ -99,6 +99,9 @@ export const WorldMap = ({ maxZoom: 18, worldCopyJump: true, zoomControl: true, + zoomSnap: 0.25, + zoomDelta: 0.5, + wheelPxPerZoomLevel: 120, maxBounds: [[-90, -Infinity], [90, Infinity]], maxBoundsViscosity: 0.8 }); From becee9761ffe8771de581841e2f0a0209488a95b Mon Sep 17 00:00:00 2001 From: accius Date: Mon, 2 Feb 2026 18:53:49 -0500 Subject: [PATCH 5/7] Update WorldMap.jsx --- src/components/WorldMap.jsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/WorldMap.jsx b/src/components/WorldMap.jsx index a3bb823..da58b3b 100644 --- a/src/components/WorldMap.jsx +++ b/src/components/WorldMap.jsx @@ -99,9 +99,9 @@ export const WorldMap = ({ maxZoom: 18, worldCopyJump: true, zoomControl: true, - zoomSnap: 0.25, - zoomDelta: 0.5, - wheelPxPerZoomLevel: 120, + zoomSnap: 0.1, + zoomDelta: 0.25, + wheelPxPerZoomLevel: 200, maxBounds: [[-90, -Infinity], [90, Infinity]], maxBoundsViscosity: 0.8 }); From 6035566661242d68965f9c307d25894a5fdba05e Mon Sep 17 00:00:00 2001 From: accius Date: Mon, 2 Feb 2026 19:03:33 -0500 Subject: [PATCH 6/7] fix highlighting --- src/components/WorldMap.jsx | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/components/WorldMap.jsx b/src/components/WorldMap.jsx index da58b3b..7919d78 100644 --- a/src/components/WorldMap.jsx +++ b/src/components/WorldMap.jsx @@ -267,8 +267,8 @@ export const WorldMap = ({ const freq = parseFloat(path.freq); const color = getBandColor(freq); - const isHovered = hoveredSpot && hoveredSpot.call === path.dxCall && - Math.abs(parseFloat(hoveredSpot.freq) - parseFloat(path.freq)) < 0.01; + const isHovered = hoveredSpot && + hoveredSpot.call?.toUpperCase() === path.dxCall?.toUpperCase(); // Handle path rendering (single continuous array, unwrapped across antimeridian) if (pathPoints && Array.isArray(pathPoints) && pathPoints.length > 1) { @@ -288,7 +288,7 @@ export const WorldMap = ({ // Add DX marker const dxCircle = L.circleMarker([dxLatDisplay, dxLonDisplay], { - radius: isHovered ? 10 : 6, + radius: isHovered ? 12 : 6, fillColor: isHovered ? '#ffffff' : color, color: isHovered ? color : '#fff', weight: isHovered ? 3 : 1.5, @@ -304,11 +304,15 @@ export const WorldMap = ({ if (showDXLabels || isHovered) { const labelIcon = L.divIcon({ className: '', - html: `${path.dxCall}`, + html: `${path.dxCall}`, iconSize: null, iconAnchor: [0, 0] }); - const label = L.marker([dxLatDisplay, dxLonDisplay], { icon: labelIcon, interactive: false }).addTo(map); + const label = L.marker([dxLatDisplay, dxLonDisplay], { + icon: labelIcon, + interactive: false, + zIndexOffset: isHovered ? 10000 : 0 + }).addTo(map); dxPathsMarkersRef.current.push(label); } } catch (err) { From 585923b04f573b12782a1fe6cc9a2963138f1983 Mon Sep 17 00:00:00 2001 From: accius Date: Mon, 2 Feb 2026 19:07:51 -0500 Subject: [PATCH 7/7] show de in cluster --- src/components/DXClusterPanel.jsx | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/components/DXClusterPanel.jsx b/src/components/DXClusterPanel.jsx index 3cd50c7..10f6cb0 100644 --- a/src/components/DXClusterPanel.jsx +++ b/src/components/DXClusterPanel.jsx @@ -157,8 +157,8 @@ export const DXClusterPanel = ({ onMouseLeave={() => onHoverSpot?.(null)} style={{ display: 'grid', - gridTemplateColumns: '60px 1fr auto', - gap: '8px', + gridTemplateColumns: '55px 1fr 1fr auto', + gap: '6px', padding: '5px 6px', borderRadius: '3px', marginBottom: '2px', @@ -180,6 +180,16 @@ export const DXClusterPanel = ({ }}> {spot.call} +
+ de {spot.spotter || '?'} +
{spot.time || ''}