fix: Add coordinate validation to WSPR plugin to prevent NaN errors

Fixed critical bug causing map to render as black screen:

🐛 Bug Fix:
- Added comprehensive coordinate validation before great circle calculation
- Prevents NaN (Not a Number) errors that caused Leaflet crashes
- Validates all coordinates are finite numbers before processing

🛡️ Safeguards Added:
- Input validation: Check coordinates exist and are valid numbers
- Distance check: Use simple line for points < 0.5 degrees apart
- Math clamping: Clamp cosine values to [-1, 1] to avoid Math.acos NaN
- Antipodal check: Handle opposite-side-of-Earth edge cases
- Output validation: Verify all generated points are finite
- Fallback: Return simple line if great circle calculation fails

🔍 Edge Cases Handled:
- Same location (distance = 0)
- Very close points (< 0.5 degrees)
- Antipodal points (opposite sides of Earth)
- Invalid/missing coordinates in API data
- NaN propagation from bad input

📝 Logging:
- Console warnings for invalid data (debugging)
- Skips bad spots gracefully without crashing
- Continues processing valid spots

Error message fixed:
"Error: Invalid LatLng object: (NaN, NaN)"

The map now renders correctly with curved propagation paths!
pull/82/head
trancen 2 days ago
parent 016109a498
commit 5e342ac31c

@ -72,7 +72,21 @@ function getLineWeight(snr) {
// Calculate great circle path between two points
// Returns array of lat/lon points forming a smooth curve
function getGreatCirclePath(lat1, lon1, lat2, lon2, numPoints = 50) {
function getGreatCirclePath(lat1, lon1, lat2, lon2, numPoints = 30) {
// Validate input coordinates
if (!isFinite(lat1) || !isFinite(lon1) || !isFinite(lat2) || !isFinite(lon2)) {
console.warn('Invalid coordinates for great circle:', { lat1, lon1, lat2, lon2 });
return [[lat1, lon1], [lat2, lon2]]; // Fallback to straight line
}
// Check if points are very close (less than 1 degree)
const deltaLat = Math.abs(lat2 - lat1);
const deltaLon = Math.abs(lon2 - lon1);
if (deltaLat < 0.5 && deltaLon < 0.5) {
// Points too close, use simple line
return [[lat1, lon1], [lat2, lon2]];
}
const path = [];
// Convert to radians
@ -85,17 +99,26 @@ function getGreatCirclePath(lat1, lon1, lat2, lon2, numPoints = 50) {
const lon2Rad = toRad(lon2);
// Calculate great circle distance
const d = Math.acos(
Math.sin(lat1Rad) * Math.sin(lat2Rad) +
Math.cos(lat1Rad) * Math.cos(lat2Rad) * Math.cos(lon2Rad - lon1Rad)
);
const cosD = Math.sin(lat1Rad) * Math.sin(lat2Rad) +
Math.cos(lat1Rad) * Math.cos(lat2Rad) * Math.cos(lon2Rad - lon1Rad);
// Clamp to [-1, 1] to avoid NaN from Math.acos
const d = Math.acos(Math.max(-1, Math.min(1, cosD)));
// Check if distance is too small or points are antipodal
if (d < 0.01 || Math.abs(d - Math.PI) < 0.01) {
// Use simple line for very small or antipodal distances
return [[lat1, lon1], [lat2, lon2]];
}
const sinD = Math.sin(d);
// Generate intermediate points along the great circle
for (let i = 0; i <= numPoints; i++) {
const f = i / numPoints;
const A = Math.sin((1 - f) * d) / Math.sin(d);
const B = Math.sin(f * d) / Math.sin(d);
const A = Math.sin((1 - f) * d) / sinD;
const B = Math.sin(f * d) / sinD;
const x = A * Math.cos(lat1Rad) * Math.cos(lon1Rad) + B * Math.cos(lat2Rad) * Math.cos(lon2Rad);
const y = A * Math.cos(lat1Rad) * Math.sin(lon1Rad) + B * Math.cos(lat2Rad) * Math.sin(lon2Rad);
@ -104,8 +127,16 @@ function getGreatCirclePath(lat1, lon1, lat2, lon2, numPoints = 50) {
const lat = toDeg(Math.atan2(z, Math.sqrt(x * x + y * y)));
const lon = toDeg(Math.atan2(y, x));
// Validate computed point
if (isFinite(lat) && isFinite(lon)) {
path.push([lat, lon]);
}
}
// If path generation failed, fall back to straight line
if (path.length < 2) {
return [[lat1, lon1], [lat2, lon2]];
}
return path;
}
@ -169,14 +200,31 @@ export function useLayer({ enabled = false, opacity = 0.7, map = null }) {
const limitedData = wsprData.slice(0, 500);
limitedData.forEach(spot => {
// Validate spot coordinates
if (!spot.senderLat || !spot.senderLon || !spot.receiverLat || !spot.receiverLon) {
console.warn('[WSPR] Skipping spot with invalid coordinates:', spot);
return;
}
// Ensure coordinates are valid numbers
const sLat = parseFloat(spot.senderLat);
const sLon = parseFloat(spot.senderLon);
const rLat = parseFloat(spot.receiverLat);
const rLon = parseFloat(spot.receiverLon);
if (!isFinite(sLat) || !isFinite(sLon) || !isFinite(rLat) || !isFinite(rLon)) {
console.warn('[WSPR] Skipping spot with non-finite coordinates:', { sLat, sLon, rLat, rLon });
return;
}
// Calculate great circle path for curved line
const pathCoords = getGreatCirclePath(
spot.senderLat,
spot.senderLon,
spot.receiverLat,
spot.receiverLon,
30 // Number of points for smooth curve
);
const pathCoords = getGreatCirclePath(sLat, sLon, rLat, rLon, 30);
// Skip if path is invalid
if (!pathCoords || pathCoords.length < 2) {
console.warn('[WSPR] Invalid path coordinates generated');
return;
}
const path = L.polyline(pathCoords, {
color: getSNRColor(spot.snr),
@ -210,7 +258,7 @@ export function useLayer({ enabled = false, opacity = 0.7, map = null }) {
if (!txStations.has(txKey)) {
txStations.add(txKey);
const txMarker = L.circleMarker([spot.senderLat, spot.senderLon], {
const txMarker = L.circleMarker([sLat, sLon], {
radius: 4,
fillColor: '#ff6600',
color: '#ffffff',
@ -228,7 +276,7 @@ export function useLayer({ enabled = false, opacity = 0.7, map = null }) {
if (!rxStations.has(rxKey)) {
rxStations.add(rxKey);
const rxMarker = L.circleMarker([spot.receiverLat, spot.receiverLon], {
const rxMarker = L.circleMarker([rLat, rLon], {
radius: 4,
fillColor: '#0088ff',
color: '#ffffff',

Loading…
Cancel
Save

Powered by TurnKey Linux.