world-sdr / index.html
kolaslab's picture
Update index.html
a817a6f verified
raw
history blame
17.3 kB
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Global SDR Network Monitor</title>
<style>
body {
margin: 0;
padding: 20px;
background: #000;
color: #0f0;
font-family: monospace;
overflow: hidden;
}
.container {
display: grid;
grid-template-columns: 300px 1fr;
gap: 20px;
}
.sidebar {
background: #111;
padding: 15px;
border-radius: 8px;
height: calc(100vh - 40px);
overflow-y: auto;
}
.receiver {
margin: 10px 0;
padding: 10px;
background: #1a1a1a;
border-radius: 4px;
position: relative;
}
.status {
display: flex;
align-items: center;
margin-bottom: 5px;
}
.led {
width: 8px;
height: 8px;
border-radius: 50%;
margin-right: 8px;
}
.active {
background: #0f0;
box-shadow: 0 0 10px #0f0;
animation: pulse 2s infinite;
}
@keyframes pulse {
0% { opacity: 1; }
50% { opacity: 0.5; }
100% { opacity: 1; }
}
.inactive {
background: #f00;
}
#map {
background: #111;
border-radius: 8px;
height: calc(100vh - 40px);
}
.signal-strength {
height: 4px;
background: #222;
margin-top: 5px;
border-radius: 2px;
}
.signal-bar {
height: 100%;
background: #0f0;
width: 50%;
transition: width 0.3s;
}
.detection {
padding: 5px;
margin: 5px 0;
font-size: 12px;
border-left: 2px solid #0f0;
}
</style>
</head>
<body>
<div class="container">
<div class="sidebar">
<h3>Active SDR Receivers</h3>
<div id="receivers"></div>
<h3>Real-time Detections</h3>
<div id="detections"></div>
</div>
<canvas id="map"></canvas>
</div>
<script>
// ์ „์„ธ๊ณ„ WebSDR ์Šคํ…Œ์ด์…˜ ๋ฐ์ดํ„ฐ
const sdrStations = [
// ์œ ๋Ÿฝ
{
name: "Twente WebSDR",
url: "websdr.ewi.utwente.nl:8901",
location: [52.2389, 6.8343],
frequency: "0-29.160 MHz",
range: 200,
active: true,
region: "Europe"
},
{
name: "TU Delft WebSDR",
url: "websdr.tudelft.nl:8901",
location: [51.9981, 4.3731],
frequency: "0-29.160 MHz",
range: 180,
active: true,
region: "Europe"
},
// ๋ถ๋ฏธ
{
name: "W6YXP WebSDR",
url: "w6yxp.stanford.edu",
location: [37.4275, -122.1697],
frequency: "0-30 MHz",
range: 200,
active: true,
region: "North America"
},
{
name: "K3FEF WebSDR",
url: "k3fef.com:8901",
location: [40.5697, -75.9363],
frequency: "0-30 MHz",
range: 180,
active: true,
region: "North America"
},
// ์•„์‹œ์•„
{
name: "Tokyo WebSDR",
url: "tokyo.websdr.org",
location: [35.6762, 139.6503],
frequency: "0-30 MHz",
range: 200,
active: true,
region: "Asia"
},
{
name: "Seoul WebSDR",
url: "seoul.websdr.org",
location: [37.5665, 126.9780],
frequency: "0-30 MHz",
range: 180,
active: true,
region: "Asia"
},
// ์˜ค์„ธ์•„๋‹ˆ์•„
{
name: "Sydney WebSDR",
url: "sydney.websdr.org",
location: [-33.8688, 151.2093],
frequency: "0-30 MHz",
range: 200,
active: true,
region: "Oceania"
},
// ๋‚จ๋ฏธ
{
name: "Sรฃo Paulo WebSDR",
url: "saopaulo.websdr.org",
location: [-23.5505, -46.6333],
frequency: "0-30 MHz",
range: 180,
active: true,
region: "South America"
}
];
class GlobalRadarSystem {
constructor() {
this.canvas = document.getElementById('map');
this.ctx = this.canvas.getContext('2d');
this.targets = new Set();
this.setupCanvas();
this.renderReceivers();
this.startTracking();
this.loadWorldMap();
}
setupCanvas() {
this.canvas.width = this.canvas.offsetWidth;
this.canvas.height = this.canvas.offsetHeight;
window.addEventListener('resize', () => {
this.canvas.width = this.canvas.offsetWidth;
this.canvas.height = this.canvas.offsetHeight;
});
}
async loadWorldMap() {
// ๊ฐ„๋‹จํ•œ ์„ธ๊ณ„์ง€๋„ ๊ทธ๋ฆฌ๋“œ ์ƒ์„ฑ
this.worldGrid = {
latLines: [],
lonLines: []
};
// ์œ„๋„์„  (15๋„ ๊ฐ„๊ฒฉ)
for(let lat = -75; lat <= 75; lat += 15) {
this.worldGrid.latLines.push(lat);
}
// ๊ฒฝ๋„์„  (30๋„ ๊ฐ„๊ฒฉ)
for(let lon = -180; lon <= 180; lon += 30) {
this.worldGrid.lonLines.push(lon);
}
}
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};
}
drawWorldMap() {
// ๋ฐฐ๊ฒฝ
this.ctx.fillStyle = '#111';
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
// ๊ทธ๋ฆฌ๋“œ ๊ทธ๋ฆฌ๊ธฐ
this.ctx.strokeStyle = '#1a1a1a';
this.ctx.lineWidth = 1;
// ์œ„๋„์„ 
this.worldGrid.latLines.forEach(lat => {
this.ctx.beginPath();
const start = this.latLongToXY(lat, -180);
const end = this.latLongToXY(lat, 180);
this.ctx.moveTo(start.x, start.y);
this.ctx.lineTo(end.x, end.y);
this.ctx.stroke();
});
// ๊ฒฝ๋„์„ 
this.worldGrid.lonLines.forEach(lon => {
this.ctx.beginPath();
const start = this.latLongToXY(-75, lon);
const end = this.latLongToXY(75, lon);
this.ctx.moveTo(start.x, start.y);
this.ctx.lineTo(end.x, end.y);
this.ctx.stroke();
});
}
renderReceivers() {
const container = document.getElementById('receivers');
const groupedStations = {};
// ์ง€์—ญ๋ณ„๋กœ ์Šคํ…Œ์ด์…˜ ๊ทธ๋ฃนํ™”
sdrStations.forEach(station => {
if (!groupedStations[station.region]) {
groupedStations[station.region] = [];
}
groupedStations[station.region].push(station);
});
// ์ง€์—ญ๋ณ„๋กœ HTML ์ƒ์„ฑ
container.innerHTML = Object.entries(groupedStations).map(([region, stations]) => `
<h4>${region}</h4>
${stations.map(station => `
<div class="receiver" id="rx-${station.url.split(':')[0]}">
<div class="status">
<div class="led ${station.active ? 'active' : 'inactive'}"></div>
<strong>${station.name}</strong>
</div>
<div>๐Ÿ“ก ${station.url}</div>
<div>๐Ÿ“ป ${station.frequency}</div>
<div>๐Ÿ“ ${station.location.join(', ')}</div>
<div>Range: ${station.range}km</div>
<div class="signal-strength">
<div class="signal-bar"></div>
</div>
</div>
`).join('')}
`).join('');
}
generateTarget() {
// ์ „ ์„ธ๊ณ„ ๋ฒ”์œ„์˜ ํƒ€๊ฒŸ ์ƒ์„ฑ
return {
type: Math.random() > 0.7 ? 'aircraft' : 'vehicle',
position: {
lat: -75 + Math.random() * 150, // -75 to 75
lon: -180 + Math.random() * 360 // -180 to 180
},
speed: Math.random() * 500 + 200,
altitude: Math.random() * 35000 + 5000,
heading: Math.random() * 360,
id: Math.random().toString(36).substr(2, 6).toUpperCase(),
signalStrength: Math.random()
};
}
drawStations() {
sdrStations.forEach(station => {
const pos = this.latLongToXY(station.location[0], station.location[1]);
// ์ปค๋ฒ„๋ฆฌ์ง€ ๋ฒ”์œ„ ๊ทธ๋ฆฌ๊ธฐ
this.ctx.beginPath();
this.ctx.arc(pos.x, pos.y, station.range / 10, 0, Math.PI * 2);
this.ctx.strokeStyle = `rgba(0,255,0,${station.active ? 0.2 : 0.1})`;
this.ctx.stroke();
// ์Šคํ…Œ์ด์…˜ ํฌ์ธํŠธ ๊ทธ๋ฆฌ๊ธฐ
this.ctx.beginPath();
this.ctx.arc(pos.x, pos.y, 4, 0, Math.PI * 2);
this.ctx.fillStyle = station.active ? '#0f0' : '#f00';
this.ctx.fill();
// ์Šคํ…Œ์ด์…˜ ๋ ˆ์ด๋ธ” ๊ทธ๋ฆฌ๊ธฐ
this.ctx.fillStyle = '#0f0';
this.ctx.font = '10px monospace';
this.ctx.fillText(station.name, pos.x + 10, pos.y + 4);
});
}
drawTargets() {
this.targets.forEach(target => {
const pos = this.latLongToXY(target.position.lat, target.position.lon);
// ํƒ€๊ฒŸ๊ณผ ๊ฐ€๊นŒ์šด ์Šคํ…Œ์ด์…˜๊ณผ์˜ ์—ฐ๊ฒฐ์„  ๊ทธ๋ฆฌ๊ธฐ
sdrStations.forEach(station => {
if(station.active) {
const stationPos = this.latLongToXY(station.location[0], station.location[1]);
const distance = Math.hypot(pos.x - stationPos.x, pos.y - stationPos.y);
if(distance < station.range / 5) {
this.ctx.beginPath();
this.ctx.strokeStyle = `rgba(0,255,0,${target.signalStrength * 0.3})`;
this.ctx.moveTo(stationPos.x, stationPos.y);
this.ctx.lineTo(pos.x, pos.y);
this.ctx.stroke();
}
}
});
// ํƒ€๊ฒŸ ๊ทธ๋ฆฌ๊ธฐ
this.ctx.beginPath();
this.ctx.arc(pos.x, pos.y, 3, 0, Math.PI * 2);
this.ctx.fillStyle = target.type === 'aircraft' ? '#ff0' : '#0ff';
this.ctx.fill();
// ํƒ€๊ฒŸ ์ •๋ณด ํ‘œ์‹œ
this.ctx.fillStyle = '#666';
this.ctx.font = '10px monospace';
this.ctx.fillText(
`${target.id} โ€ข ${target.speed.toFixed(0)}kts โ€ข ${target.altitude.toFixed(0)}ft`,
pos.x + 10,
pos.y + 4
);
});
}
updateDetections() {
const detections = document.getElementById('detections');
detections.innerHTML = Array.from(this.targets)
.map(target => `
<div class="detection">
${target.type === 'aircraft' ? 'โœˆ๏ธ' : '๐Ÿš—'}
${target.id}
${target.speed.toFixed(0)}kts
${target.type === 'aircraft' ? `${target.altitude.toFixed(0)}ft` : ''}
Signal: ${(target.signalStrength * 100).toFixed(0)}%
Position: ${target.position.lat.toFixed(2)}ยฐ, ${target.position.lon.to
updateDetections() {
const detections = document.getElementById('detections');
detections.innerHTML = Array.from(this.targets)
.map(target => `
<div class="detection">
${target.type === 'aircraft' ? 'โœˆ๏ธ' : '๐Ÿš—'}
${target.id}
${target.speed.toFixed(0)}kts
${target.type === 'aircraft' ? `${target.altitude.toFixed(0)}ft` : ''}
Signal: ${(target.signalStrength * 100).toFixed(0)}%
Position: ${target.position.lat.toFixed(2)}ยฐ, ${target.position.lon.toFixed(2)}ยฐ
</div>
`).join('');
}
updateSignalStrengths() {
sdrStations.forEach(station => {
const bar = document.querySelector(`#rx-${station.url.split(':')[0]} .signal-bar`);
if(bar) {
const strength = 40 + Math.random() * 60;
bar.style.width = `${strength}%`;
}
});
}
startTracking() {
setInterval(() => {
// ํƒ€๊ฒŸ ์ถ”๊ฐ€/์ œ๊ฑฐ
if(Math.random() < 0.1 && this.targets.size < 15) {
this.targets.add(this.generateTarget());
}
if(Math.random() < 0.1 && this.targets.size > 0) {
this.targets.delete(Array.from(this.targets)[0]);
}
// ๊ธฐ์กด ํƒ€๊ฒŸ ์—…๋ฐ์ดํŠธ (์›€์ง์ž„ ์‹œ๋ฎฌ๋ ˆ์ด์…˜)
this.targets.forEach(target => {
// ํ˜„์žฌ ๋ฐฉํ–ฅ์œผ๋กœ ์ด๋™
const speed = target.speed / 3600; // kts to degrees per second (approximate)
const radians = target.heading * Math.PI / 180;
target.position.lon += Math.sin(radians) * speed;
target.position.lat += Math.cos(radians) * speed;
// ํ™”๋ฉด ๊ฒฝ๊ณ„ ์ฒ˜๋ฆฌ
if(target.position.lon > 180) target.position.lon -= 360;
if(target.position.lon < -180) target.position.lon += 360;
if(target.position.lat > 75) target.position.lat = 75;
if(target.position.lat < -75) target.position.lat = -75;
// ๊ฐ€๋” ๋ฐฉํ–ฅ ๋ณ€๊ฒฝ
if(Math.random() < 0.05) {
target.heading += (Math.random() - 0.5) * 30;
target.heading = (target.heading + 360) % 360;
}
// ์‹ ํ˜ธ ๊ฐ•๋„ ๋ณ€๋™
target.signalStrength = Math.max(0.1, Math.min(1, target.signalStrength + (Math.random() - 0.5) * 0.1));
});
this.drawWorldMap();
this.drawStations();
this.drawTargets();
this.updateDetections();
this.updateSignalStrengths();
}, 100);
}
}
// Initialize global radar system
const radar = new GlobalRadarSystem();
</script>
</body>
</html>