|
|
|
@ -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
|
|
|
|
|