|
<!DOCTYPE html> |
|
<html> |
|
<head> |
|
<meta charset="UTF-8"> |
|
<title>Basic SDR Network Monitor</title> |
|
<style> |
|
body { |
|
margin: 0; |
|
padding: 20px; |
|
background: #000; |
|
color: #0f0; |
|
font-family: monospace; |
|
} |
|
|
|
.container { |
|
display: grid; |
|
grid-template-columns: 300px 1fr; |
|
gap: 20px; |
|
height: calc(100vh - 40px); |
|
} |
|
|
|
.sidebar { |
|
background: #111; |
|
padding: 15px; |
|
border-radius: 8px; |
|
overflow-y: auto; |
|
} |
|
|
|
.map-container { |
|
background: #111; |
|
border-radius: 8px; |
|
position: relative; |
|
} |
|
|
|
.receiver { |
|
margin: 10px 0; |
|
padding: 10px; |
|
background: #1a1a1a; |
|
border-radius: 4px; |
|
} |
|
|
|
.status { |
|
display: flex; |
|
align-items: center; |
|
margin-bottom: 5px; |
|
} |
|
|
|
.led { |
|
width: 8px; |
|
height: 8px; |
|
border-radius: 50%; |
|
margin-right: 8px; |
|
background: #0f0; |
|
} |
|
|
|
canvas { |
|
width: 100%; |
|
height: 100%; |
|
} |
|
</style> |
|
</head> |
|
<body> |
|
<div class="container"> |
|
<div class="sidebar"> |
|
<h3>SDR Receivers</h3> |
|
<div id="receivers"></div> |
|
</div> |
|
<div class="map-container"> |
|
<canvas id="map"></canvas> |
|
</div> |
|
</div> |
|
|
|
<script> |
|
|
|
const stations = [ |
|
{ |
|
name: "Twente WebSDR", |
|
location: [52.2389, 6.8343], |
|
active: true |
|
}, |
|
{ |
|
name: "TU Delft WebSDR", |
|
location: [51.9981, 4.3731], |
|
active: true |
|
}, |
|
{ |
|
name: "W6YXP WebSDR", |
|
location: [37.4275, -122.1697], |
|
active: true |
|
}, |
|
{ |
|
name: "Tokyo WebSDR", |
|
location: [35.6762, 139.6503], |
|
active: true |
|
} |
|
]; |
|
|
|
class SDRMonitor { |
|
constructor() { |
|
this.canvas = document.getElementById('map'); |
|
this.ctx = this.canvas.getContext('2d'); |
|
this.setupCanvas(); |
|
this.renderStations(); |
|
this.drawMap(); |
|
} |
|
|
|
setupCanvas() { |
|
|
|
const updateSize = () => { |
|
const container = this.canvas.parentElement; |
|
this.canvas.width = container.offsetWidth; |
|
this.canvas.height = container.offsetHeight; |
|
}; |
|
|
|
updateSize(); |
|
window.addEventListener('resize', () => { |
|
updateSize(); |
|
this.drawMap(); |
|
}); |
|
} |
|
|
|
renderStations() { |
|
const container = document.getElementById('receivers'); |
|
container.innerHTML = stations.map(station => ` |
|
<div class="receiver"> |
|
<div class="status"> |
|
<div class="led"></div> |
|
<strong>${station.name}</strong> |
|
</div> |
|
<div>Location: ${station.location.join(', ')}</div> |
|
</div> |
|
`).join(''); |
|
} |
|
|
|
latLongToXY(lat, lon) { |
|
const mapWidth = this.canvas.width; |
|
const mapHeight = this.canvas.height; |
|
|
|
const x = (lon + 180) * (mapWidth / 360); |
|
const latRad = lat * Math.PI / 180; |
|
const mercN = Math.log(Math.tan((Math.PI / 4) + (latRad / 2))); |
|
const y = (mapHeight / 2) - (mapWidth * mercN / (2 * Math.PI)); |
|
|
|
return {x, y}; |
|
} |
|
|
|
drawMap() { |
|
|
|
this.ctx.fillStyle = '#111'; |
|
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height); |
|
|
|
|
|
this.ctx.strokeStyle = '#1a1a1a'; |
|
this.ctx.lineWidth = 1; |
|
|
|
|
|
for(let lon = -180; lon <= 180; lon += 30) { |
|
const start = this.latLongToXY(-80, lon); |
|
const end = this.latLongToXY(80, lon); |
|
this.ctx.beginPath(); |
|
this.ctx.moveTo(start.x, start.y); |
|
this.ctx.lineTo(end.x, end.y); |
|
this.ctx.stroke(); |
|
} |
|
|
|
|
|
for(let lat = -80; lat <= 80; lat += 20) { |
|
const start = this.latLongToXY(lat, -180); |
|
const end = this.latLongToXY(lat, 180); |
|
this.ctx.beginPath(); |
|
this.ctx.moveTo(start.x, start.y); |
|
this.ctx.lineTo(end.x, end.y); |
|
this.ctx.stroke(); |
|
} |
|
|
|
|
|
stations.forEach(station => { |
|
const pos = this.latLongToXY(station.location[0], station.location[1]); |
|
|
|
|
|
this.ctx.beginPath(); |
|
this.ctx.arc(pos.x, pos.y, 5, 0, Math.PI * 2); |
|
this.ctx.fillStyle = '#0f0'; |
|
this.ctx.fill(); |
|
|
|
|
|
this.ctx.fillStyle = '#0f0'; |
|
this.ctx.font = '12px monospace'; |
|
this.ctx.fillText(station.name, pos.x + 10, pos.y + 5); |
|
}); |
|
} |
|
} |
|
|
|
|
|
const monitor = new SDRMonitor(); |
|
</script> |
|
</body> |
|
</html> |