kolaslab commited on
Commit
a817a6f
·
verified ·
1 Parent(s): 0e4238b

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +392 -1
index.html CHANGED
@@ -1,4 +1,395 @@
1
- updateDetections() {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
  const detections = document.getElementById('detections');
3
  detections.innerHTML = Array.from(this.targets)
4
  .map(target => `
 
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>Global SDR Network Monitor</title>
6
+ <style>
7
+ body {
8
+ margin: 0;
9
+ padding: 20px;
10
+ background: #000;
11
+ color: #0f0;
12
+ font-family: monospace;
13
+ overflow: hidden;
14
+ }
15
+
16
+ .container {
17
+ display: grid;
18
+ grid-template-columns: 300px 1fr;
19
+ gap: 20px;
20
+ }
21
+
22
+ .sidebar {
23
+ background: #111;
24
+ padding: 15px;
25
+ border-radius: 8px;
26
+ height: calc(100vh - 40px);
27
+ overflow-y: auto;
28
+ }
29
+
30
+ .receiver {
31
+ margin: 10px 0;
32
+ padding: 10px;
33
+ background: #1a1a1a;
34
+ border-radius: 4px;
35
+ position: relative;
36
+ }
37
+
38
+ .status {
39
+ display: flex;
40
+ align-items: center;
41
+ margin-bottom: 5px;
42
+ }
43
+
44
+ .led {
45
+ width: 8px;
46
+ height: 8px;
47
+ border-radius: 50%;
48
+ margin-right: 8px;
49
+ }
50
+
51
+ .active {
52
+ background: #0f0;
53
+ box-shadow: 0 0 10px #0f0;
54
+ animation: pulse 2s infinite;
55
+ }
56
+
57
+ @keyframes pulse {
58
+ 0% { opacity: 1; }
59
+ 50% { opacity: 0.5; }
60
+ 100% { opacity: 1; }
61
+ }
62
+
63
+ .inactive {
64
+ background: #f00;
65
+ }
66
+
67
+ #map {
68
+ background: #111;
69
+ border-radius: 8px;
70
+ height: calc(100vh - 40px);
71
+ }
72
+
73
+ .signal-strength {
74
+ height: 4px;
75
+ background: #222;
76
+ margin-top: 5px;
77
+ border-radius: 2px;
78
+ }
79
+
80
+ .signal-bar {
81
+ height: 100%;
82
+ background: #0f0;
83
+ width: 50%;
84
+ transition: width 0.3s;
85
+ }
86
+
87
+ .detection {
88
+ padding: 5px;
89
+ margin: 5px 0;
90
+ font-size: 12px;
91
+ border-left: 2px solid #0f0;
92
+ }
93
+ </style>
94
+ </head>
95
+ <body>
96
+ <div class="container">
97
+ <div class="sidebar">
98
+ <h3>Active SDR Receivers</h3>
99
+ <div id="receivers"></div>
100
+
101
+ <h3>Real-time Detections</h3>
102
+ <div id="detections"></div>
103
+ </div>
104
+
105
+ <canvas id="map"></canvas>
106
+ </div>
107
+
108
+ <script>
109
+ // 전세계 WebSDR 스테이션 데이터
110
+ const sdrStations = [
111
+ // 유럽
112
+ {
113
+ name: "Twente WebSDR",
114
+ url: "websdr.ewi.utwente.nl:8901",
115
+ location: [52.2389, 6.8343],
116
+ frequency: "0-29.160 MHz",
117
+ range: 200,
118
+ active: true,
119
+ region: "Europe"
120
+ },
121
+ {
122
+ name: "TU Delft WebSDR",
123
+ url: "websdr.tudelft.nl:8901",
124
+ location: [51.9981, 4.3731],
125
+ frequency: "0-29.160 MHz",
126
+ range: 180,
127
+ active: true,
128
+ region: "Europe"
129
+ },
130
+ // 북미
131
+ {
132
+ name: "W6YXP WebSDR",
133
+ url: "w6yxp.stanford.edu",
134
+ location: [37.4275, -122.1697],
135
+ frequency: "0-30 MHz",
136
+ range: 200,
137
+ active: true,
138
+ region: "North America"
139
+ },
140
+ {
141
+ name: "K3FEF WebSDR",
142
+ url: "k3fef.com:8901",
143
+ location: [40.5697, -75.9363],
144
+ frequency: "0-30 MHz",
145
+ range: 180,
146
+ active: true,
147
+ region: "North America"
148
+ },
149
+ // 아시아
150
+ {
151
+ name: "Tokyo WebSDR",
152
+ url: "tokyo.websdr.org",
153
+ location: [35.6762, 139.6503],
154
+ frequency: "0-30 MHz",
155
+ range: 200,
156
+ active: true,
157
+ region: "Asia"
158
+ },
159
+ {
160
+ name: "Seoul WebSDR",
161
+ url: "seoul.websdr.org",
162
+ location: [37.5665, 126.9780],
163
+ frequency: "0-30 MHz",
164
+ range: 180,
165
+ active: true,
166
+ region: "Asia"
167
+ },
168
+ // 오세아니아
169
+ {
170
+ name: "Sydney WebSDR",
171
+ url: "sydney.websdr.org",
172
+ location: [-33.8688, 151.2093],
173
+ frequency: "0-30 MHz",
174
+ range: 200,
175
+ active: true,
176
+ region: "Oceania"
177
+ },
178
+ // 남미
179
+ {
180
+ name: "São Paulo WebSDR",
181
+ url: "saopaulo.websdr.org",
182
+ location: [-23.5505, -46.6333],
183
+ frequency: "0-30 MHz",
184
+ range: 180,
185
+ active: true,
186
+ region: "South America"
187
+ }
188
+ ];
189
+
190
+ class GlobalRadarSystem {
191
+ constructor() {
192
+ this.canvas = document.getElementById('map');
193
+ this.ctx = this.canvas.getContext('2d');
194
+ this.targets = new Set();
195
+ this.setupCanvas();
196
+ this.renderReceivers();
197
+ this.startTracking();
198
+ this.loadWorldMap();
199
+ }
200
+
201
+ setupCanvas() {
202
+ this.canvas.width = this.canvas.offsetWidth;
203
+ this.canvas.height = this.canvas.offsetHeight;
204
+
205
+ window.addEventListener('resize', () => {
206
+ this.canvas.width = this.canvas.offsetWidth;
207
+ this.canvas.height = this.canvas.offsetHeight;
208
+ });
209
+ }
210
+
211
+ async loadWorldMap() {
212
+ // 간단한 세계지도 그리드 생성
213
+ this.worldGrid = {
214
+ latLines: [],
215
+ lonLines: []
216
+ };
217
+
218
+ // 위도선 (15도 간격)
219
+ for(let lat = -75; lat <= 75; lat += 15) {
220
+ this.worldGrid.latLines.push(lat);
221
+ }
222
+
223
+ // 경도선 (30도 간격)
224
+ for(let lon = -180; lon <= 180; lon += 30) {
225
+ this.worldGrid.lonLines.push(lon);
226
+ }
227
+ }
228
+
229
+ latLongToXY(lat, lon) {
230
+ // 메르카토르 투영법 사용
231
+ const mapWidth = this.canvas.width;
232
+ const mapHeight = this.canvas.height;
233
+
234
+ const x = (lon + 180) * (mapWidth / 360);
235
+ const latRad = lat * Math.PI / 180;
236
+ const mercN = Math.log(Math.tan((Math.PI / 4) + (latRad / 2)));
237
+ const y = (mapHeight / 2) - (mapWidth * mercN / (2 * Math.PI));
238
+
239
+ return {x, y};
240
+ }
241
+
242
+ drawWorldMap() {
243
+ // 배경
244
+ this.ctx.fillStyle = '#111';
245
+ this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
246
+
247
+ // 그리드 그리기
248
+ this.ctx.strokeStyle = '#1a1a1a';
249
+ this.ctx.lineWidth = 1;
250
+
251
+ // 위도선
252
+ this.worldGrid.latLines.forEach(lat => {
253
+ this.ctx.beginPath();
254
+ const start = this.latLongToXY(lat, -180);
255
+ const end = this.latLongToXY(lat, 180);
256
+ this.ctx.moveTo(start.x, start.y);
257
+ this.ctx.lineTo(end.x, end.y);
258
+ this.ctx.stroke();
259
+ });
260
+
261
+ // 경도선
262
+ this.worldGrid.lonLines.forEach(lon => {
263
+ this.ctx.beginPath();
264
+ const start = this.latLongToXY(-75, lon);
265
+ const end = this.latLongToXY(75, lon);
266
+ this.ctx.moveTo(start.x, start.y);
267
+ this.ctx.lineTo(end.x, end.y);
268
+ this.ctx.stroke();
269
+ });
270
+ }
271
+
272
+ renderReceivers() {
273
+ const container = document.getElementById('receivers');
274
+ const groupedStations = {};
275
+
276
+ // 지역별로 스테이션 그룹화
277
+ sdrStations.forEach(station => {
278
+ if (!groupedStations[station.region]) {
279
+ groupedStations[station.region] = [];
280
+ }
281
+ groupedStations[station.region].push(station);
282
+ });
283
+
284
+ // 지역별로 HTML 생성
285
+ container.innerHTML = Object.entries(groupedStations).map(([region, stations]) => `
286
+ <h4>${region}</h4>
287
+ ${stations.map(station => `
288
+ <div class="receiver" id="rx-${station.url.split(':')[0]}">
289
+ <div class="status">
290
+ <div class="led ${station.active ? 'active' : 'inactive'}"></div>
291
+ <strong>${station.name}</strong>
292
+ </div>
293
+ <div>📡 ${station.url}</div>
294
+ <div>📻 ${station.frequency}</div>
295
+ <div>📍 ${station.location.join(', ')}</div>
296
+ <div>Range: ${station.range}km</div>
297
+ <div class="signal-strength">
298
+ <div class="signal-bar"></div>
299
+ </div>
300
+ </div>
301
+ `).join('')}
302
+ `).join('');
303
+ }
304
+
305
+ generateTarget() {
306
+ // 전 세계 범위의 타겟 생성
307
+ return {
308
+ type: Math.random() > 0.7 ? 'aircraft' : 'vehicle',
309
+ position: {
310
+ lat: -75 + Math.random() * 150, // -75 to 75
311
+ lon: -180 + Math.random() * 360 // -180 to 180
312
+ },
313
+ speed: Math.random() * 500 + 200,
314
+ altitude: Math.random() * 35000 + 5000,
315
+ heading: Math.random() * 360,
316
+ id: Math.random().toString(36).substr(2, 6).toUpperCase(),
317
+ signalStrength: Math.random()
318
+ };
319
+ }
320
+
321
+ drawStations() {
322
+ sdrStations.forEach(station => {
323
+ const pos = this.latLongToXY(station.location[0], station.location[1]);
324
+
325
+ // 커버리지 범위 그리기
326
+ this.ctx.beginPath();
327
+ this.ctx.arc(pos.x, pos.y, station.range / 10, 0, Math.PI * 2);
328
+ this.ctx.strokeStyle = `rgba(0,255,0,${station.active ? 0.2 : 0.1})`;
329
+ this.ctx.stroke();
330
+
331
+ // 스테이션 포인트 그리기
332
+ this.ctx.beginPath();
333
+ this.ctx.arc(pos.x, pos.y, 4, 0, Math.PI * 2);
334
+ this.ctx.fillStyle = station.active ? '#0f0' : '#f00';
335
+ this.ctx.fill();
336
+
337
+ // 스테이션 레이블 그리기
338
+ this.ctx.fillStyle = '#0f0';
339
+ this.ctx.font = '10px monospace';
340
+ this.ctx.fillText(station.name, pos.x + 10, pos.y + 4);
341
+ });
342
+ }
343
+
344
+ drawTargets() {
345
+ this.targets.forEach(target => {
346
+ const pos = this.latLongToXY(target.position.lat, target.position.lon);
347
+
348
+ // 타겟과 가까운 스테이션과의 연결선 그리기
349
+ sdrStations.forEach(station => {
350
+ if(station.active) {
351
+ const stationPos = this.latLongToXY(station.location[0], station.location[1]);
352
+ const distance = Math.hypot(pos.x - stationPos.x, pos.y - stationPos.y);
353
+ if(distance < station.range / 5) {
354
+ this.ctx.beginPath();
355
+ this.ctx.strokeStyle = `rgba(0,255,0,${target.signalStrength * 0.3})`;
356
+ this.ctx.moveTo(stationPos.x, stationPos.y);
357
+ this.ctx.lineTo(pos.x, pos.y);
358
+ this.ctx.stroke();
359
+ }
360
+ }
361
+ });
362
+
363
+ // 타겟 그리기
364
+ this.ctx.beginPath();
365
+ this.ctx.arc(pos.x, pos.y, 3, 0, Math.PI * 2);
366
+ this.ctx.fillStyle = target.type === 'aircraft' ? '#ff0' : '#0ff';
367
+ this.ctx.fill();
368
+
369
+ // 타겟 정보 표시
370
+ this.ctx.fillStyle = '#666';
371
+ this.ctx.font = '10px monospace';
372
+ this.ctx.fillText(
373
+ `${target.id} • ${target.speed.toFixed(0)}kts • ${target.altitude.toFixed(0)}ft`,
374
+ pos.x + 10,
375
+ pos.y + 4
376
+ );
377
+ });
378
+ }
379
+
380
+ updateDetections() {
381
+ const detections = document.getElementById('detections');
382
+ detections.innerHTML = Array.from(this.targets)
383
+ .map(target => `
384
+ <div class="detection">
385
+ ${target.type === 'aircraft' ? '✈️' : '🚗'}
386
+ ${target.id}
387
+ ${target.speed.toFixed(0)}kts
388
+ ${target.type === 'aircraft' ? `${target.altitude.toFixed(0)}ft` : ''}
389
+ Signal: ${(target.signalStrength * 100).toFixed(0)}%
390
+ Position: ${target.position.lat.toFixed(2)}°, ${target.position.lon.to
391
+
392
+ updateDetections() {
393
  const detections = document.getElementById('detections');
394
  detections.innerHTML = Array.from(this.targets)
395
  .map(target => `