Merge pull request #41 from accius/Modular-Staging

Modular staging
pull/65/head
accius 2 days ago committed by GitHub
commit 4a4758d091
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -157,8 +157,8 @@ export const DXClusterPanel = ({
onMouseLeave={() => onHoverSpot?.(null)} onMouseLeave={() => onHoverSpot?.(null)}
style={{ style={{
display: 'grid', display: 'grid',
gridTemplateColumns: '60px 1fr auto', gridTemplateColumns: '55px 1fr 1fr auto',
gap: '8px', gap: '6px',
padding: '5px 6px', padding: '5px 6px',
borderRadius: '3px', borderRadius: '3px',
marginBottom: '2px', marginBottom: '2px',
@ -180,6 +180,16 @@ export const DXClusterPanel = ({
}}> }}>
{spot.call} {spot.call}
</div> </div>
<div style={{
color: 'var(--text-muted)',
fontSize: '10px',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
alignSelf: 'center'
}}>
de {spot.spotter || '?'}
</div>
<div style={{ color: 'var(--text-muted)', fontSize: '10px' }}> <div style={{ color: 'var(--text-muted)', fontSize: '10px' }}>
{spot.time || ''} {spot.time || ''}
</div> </div>

@ -99,6 +99,9 @@ export const WorldMap = ({
maxZoom: 18, maxZoom: 18,
worldCopyJump: true, worldCopyJump: true,
zoomControl: true, zoomControl: true,
zoomSnap: 0.1,
zoomDelta: 0.25,
wheelPxPerZoomLevel: 200,
maxBounds: [[-90, -Infinity], [90, Infinity]], maxBounds: [[-90, -Infinity], [90, Infinity]],
maxBoundsViscosity: 0.8 maxBoundsViscosity: 0.8
}); });
@ -264,16 +267,12 @@ export const WorldMap = ({
const freq = parseFloat(path.freq); const freq = parseFloat(path.freq);
const color = getBandColor(freq); const color = getBandColor(freq);
const isHovered = hoveredSpot && hoveredSpot.call === path.dxCall && const isHovered = hoveredSpot &&
Math.abs(parseFloat(hoveredSpot.freq) - parseFloat(path.freq)) < 0.01; hoveredSpot.call?.toUpperCase() === path.dxCall?.toUpperCase();
// Handle segments // Handle path rendering (single continuous array, unwrapped across antimeridian)
const isSegmented = Array.isArray(pathPoints[0]) && pathPoints[0].length > 0 && Array.isArray(pathPoints[0][0]); if (pathPoints && Array.isArray(pathPoints) && pathPoints.length > 1) {
const segments = isSegmented ? pathPoints : [pathPoints]; const line = L.polyline(pathPoints, {
segments.forEach(segment => {
if (segment && Array.isArray(segment) && segment.length > 1) {
const line = L.polyline(segment, {
color: isHovered ? '#ffffff' : color, color: isHovered ? '#ffffff' : color,
weight: isHovered ? 4 : 1.5, weight: isHovered ? 4 : 1.5,
opacity: isHovered ? 1 : 0.5 opacity: isHovered ? 1 : 0.5
@ -281,11 +280,15 @@ export const WorldMap = ({
if (isHovered) line.bringToFront(); if (isHovered) line.bringToFront();
dxPathsLinesRef.current.push(line); 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 // Add DX marker
const dxCircle = L.circleMarker([path.dxLat, path.dxLon], { const dxCircle = L.circleMarker([dxLatDisplay, dxLonDisplay], {
radius: isHovered ? 10 : 6, radius: isHovered ? 12 : 6,
fillColor: isHovered ? '#ffffff' : color, fillColor: isHovered ? '#ffffff' : color,
color: isHovered ? color : '#fff', color: isHovered ? color : '#fff',
weight: isHovered ? 3 : 1.5, weight: isHovered ? 3 : 1.5,
@ -301,11 +304,15 @@ export const WorldMap = ({
if (showDXLabels || isHovered) { if (showDXLabels || isHovered) {
const labelIcon = L.divIcon({ const labelIcon = L.divIcon({
className: '', className: '',
html: `<span style="display:inline-block;background:${isHovered ? '#fff' : color};color:${isHovered ? color : '#000'};padding:4px 8px;border-radius:4px;font-family:'JetBrains Mono',monospace;font-size:12px;font-weight:700;white-space:nowrap;border:2px solid ${isHovered ? color : 'rgba(0,0,0,0.5)'};box-shadow:0 2px 4px rgba(0,0,0,0.4);">${path.dxCall}</span>`, html: `<span style="display:inline-block;background:${isHovered ? '#fff' : color};color:${isHovered ? color : '#000'};padding:${isHovered ? '5px 10px' : '4px 8px'};border-radius:4px;font-family:'JetBrains Mono',monospace;font-size:${isHovered ? '14px' : '12px'};font-weight:700;white-space:nowrap;border:2px solid ${isHovered ? color : 'rgba(0,0,0,0.5)'};box-shadow:0 2px ${isHovered ? '8px' : '4px'} rgba(0,0,0,${isHovered ? '0.6' : '0.4'});">${path.dxCall}</span>`,
iconSize: null, iconSize: null,
iconAnchor: [0, 0] 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,
zIndexOffset: isHovered ? 10000 : 0
}).addTo(map);
dxPathsMarkersRef.current.push(label); dxPathsMarkersRef.current.push(label);
} }
} catch (err) { } catch (err) {
@ -358,26 +365,14 @@ export const WorldMap = ({
// Draw orbit track if available // Draw orbit track if available
if (sat.track && sat.track.length > 1) { if (sat.track && sat.track.length > 1) {
// Split track into segments to handle date line crossing // Unwrap longitudes for continuous rendering across antimeridian
let segments = []; const unwrapped = sat.track.map(p => [...p]);
let currentSegment = [sat.track[0]]; for (let i = 1; i < unwrapped.length; i++) {
while (unwrapped[i][1] - unwrapped[i-1][1] > 180) unwrapped[i][1] -= 360;
for (let i = 1; i < sat.track.length; i++) { while (unwrapped[i][1] - unwrapped[i-1][1] < -180) unwrapped[i][1] += 360;
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]);
}
segments.push(currentSegment);
// Draw each segment const trackLine = L.polyline(unwrapped, {
segments.forEach(segment => {
if (segment.length > 1) {
const trackLine = L.polyline(segment, {
color: sat.visible ? satColor : satColorDark, color: sat.visible ? satColor : satColorDark,
weight: 2, weight: 2,
opacity: sat.visible ? 0.8 : 0.4, opacity: sat.visible ? 0.8 : 0.4,
@ -385,8 +380,6 @@ export const WorldMap = ({
}).addTo(map); }).addTo(map);
satTracksRef.current.push(trackLine); satTracksRef.current.push(trackLine);
} }
});
}
// Draw footprint circle if available and satellite is visible // Draw footprint circle if available and satellite is visible
if (sat.footprintRadius && sat.lat && sat.lon && sat.visible) { if (sat.footprintRadius && sat.lat && sat.lon && sat.visible) {
@ -521,8 +514,8 @@ export const WorldMap = ({
if (showPSKReporter && pskReporterSpots && pskReporterSpots.length > 0 && hasValidDE) { if (showPSKReporter && pskReporterSpots && pskReporterSpots.length > 0 && hasValidDE) {
pskReporterSpots.forEach(spot => { pskReporterSpots.forEach(spot => {
// Validate spot coordinates are valid numbers // Validate spot coordinates are valid numbers
const spotLat = parseFloat(spot.lat); let spotLat = parseFloat(spot.lat);
const spotLon = parseFloat(spot.lon); let spotLon = parseFloat(spot.lon);
if (!isNaN(spotLat) && !isNaN(spotLon)) { if (!isNaN(spotLat) && !isNaN(spotLon)) {
const displayCall = spot.receiver || spot.sender; const displayCall = spot.receiver || spot.sender;
@ -537,8 +530,9 @@ export const WorldMap = ({
50 50
); );
// Validate points before creating polyline // Validate points before creating polyline (single continuous array, unwrapped across antimeridian)
if (points && points.length > 1 && points.every(p => Array.isArray(p) && !isNaN(p[0]) && !isNaN(p[1]))) { 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, { const line = L.polyline(points, {
color: bandColor, color: bandColor,
weight: 1.5, weight: 1.5,
@ -546,6 +540,11 @@ export const WorldMap = ({
dashArray: '4, 4' dashArray: '4, 4'
}).addTo(map); }).addTo(map);
pskMarkersRef.current.push(line); 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 // Add small dot marker at spot location

@ -205,26 +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))]); 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 // Unwrap longitudes to be continuous (no jumps > 180°)
const segments = []; // This lets Leaflet draw smoothly across the antimeridian and world copies
let currentSegment = [rawPoints[0]];
for (let i = 1; i < rawPoints.length; i++) { for (let i = 1; i < rawPoints.length; i++) {
const prevLon = rawPoints[i-1][1]; while (rawPoints[i][1] - rawPoints[i-1][1] > 180) rawPoints[i][1] -= 360;
const currLon = rawPoints[i][1]; while (rawPoints[i][1] - rawPoints[i-1][1] < -180) rawPoints[i][1] += 360;
// Check if we crossed the antimeridian (lon jumps more than 180°)
if (Math.abs(currLon - prevLon) > 180) {
// Finish current segment
segments.push(currentSegment);
// Start new segment
currentSegment = [];
}
currentSegment.push(rawPoints[i]);
} }
segments.push(currentSegment);
return segments; return rawPoints;
}; };
export default { export default {

Loading…
Cancel
Save

Powered by TurnKey Linux.