kolaslab commited on
Commit
e167ec4
ยท
verified ยท
1 Parent(s): 4808b10

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +56 -492
index.html CHANGED
@@ -2,7 +2,7 @@
2
  <html>
3
  <head>
4
  <meta charset="UTF-8">
5
- <title>Global SDR Network Monitor</title>
6
  <style>
7
  body {
8
  margin: 0;
@@ -10,7 +10,6 @@
10
  background: #000;
11
  color: #0f0;
12
  font-family: monospace;
13
- overflow: hidden;
14
  }
15
 
16
  .container {
@@ -31,7 +30,6 @@
31
  background: #111;
32
  border-radius: 8px;
33
  position: relative;
34
- overflow: hidden;
35
  }
36
 
37
  .receiver {
@@ -39,11 +37,6 @@
39
  padding: 10px;
40
  background: #1a1a1a;
41
  border-radius: 4px;
42
- transition: background-color 0.3s;
43
- }
44
-
45
- .receiver:hover {
46
- background: #222;
47
  }
48
 
49
  .status {
@@ -57,578 +50,149 @@
57
  height: 8px;
58
  border-radius: 50%;
59
  margin-right: 8px;
60
- }
61
-
62
- .active {
63
  background: #0f0;
64
- box-shadow: 0 0 10px #0f0;
65
- animation: pulse 2s infinite;
66
- }
67
-
68
- @keyframes pulse {
69
- 0% { opacity: 1; }
70
- 50% { opacity: 0.5; }
71
- 100% { opacity: 1; }
72
- }
73
-
74
- .inactive {
75
- background: #f00;
76
  }
77
 
78
- #map {
79
  width: 100%;
80
  height: 100%;
81
- cursor: move;
82
- }
83
-
84
- .signal-strength {
85
- height: 4px;
86
- background: #222;
87
- margin-top: 5px;
88
- border-radius: 2px;
89
- }
90
-
91
- .signal-bar {
92
- height: 100%;
93
- background: #0f0;
94
- width: 50%;
95
- transition: width 0.3s;
96
- }
97
-
98
- .detection {
99
- padding: 5px;
100
- margin: 5px 0;
101
- font-size: 12px;
102
- border-left: 2px solid #0f0;
103
- background: #1a1a1a;
104
- border-radius: 0 4px 4px 0;
105
- transition: background-color 0.3s;
106
- }
107
-
108
- .detection:hover {
109
- background: #222;
110
- }
111
-
112
- .controls {
113
- position: absolute;
114
- bottom: 20px;
115
- right: 20px;
116
- background: rgba(0, 0, 0, 0.7);
117
- padding: 10px;
118
- border-radius: 4px;
119
- display: flex;
120
- gap: 10px;
121
- }
122
-
123
- .control-btn {
124
- background: #1a1a1a;
125
- border: 1px solid #0f0;
126
- color: #0f0;
127
- padding: 5px 10px;
128
- border-radius: 4px;
129
- cursor: pointer;
130
- transition: all 0.3s;
131
- }
132
-
133
- .control-btn:hover {
134
- background: #0f0;
135
- color: #000;
136
- }
137
-
138
- h3, h4 {
139
- color: #0f0;
140
- margin-top: 15px;
141
- margin-bottom: 10px;
142
- border-bottom: 1px solid #0f0;
143
- padding-bottom: 5px;
144
- }
145
-
146
- .region-label {
147
- display: inline-block;
148
- background: #1a1a1a;
149
- padding: 2px 6px;
150
- border-radius: 4px;
151
- font-size: 0.8em;
152
- margin-left: 10px;
153
- }
154
-
155
- .tooltip {
156
- position: absolute;
157
- background: rgba(0, 0, 0, 0.8);
158
- border: 1px solid #0f0;
159
- padding: 5px;
160
- border-radius: 4px;
161
- pointer-events: none;
162
- display: none;
163
- color: #0f0;
164
- font-size: 12px;
165
- z-index: 1000;
166
  }
167
  </style>
168
  </head>
169
  <body>
170
  <div class="container">
171
  <div class="sidebar">
172
- <h3>Active SDR Receivers</h3>
173
  <div id="receivers"></div>
174
-
175
- <h3>Real-time Detections</h3>
176
- <div id="detections"></div>
177
  </div>
178
-
179
  <div class="map-container">
180
  <canvas id="map"></canvas>
181
- <div class="controls">
182
- <button class="control-btn" id="zoomIn">Zoom In</button>
183
- <button class="control-btn" id="zoomOut">Zoom Out</button>
184
- <button class="control-btn" id="resetView">Reset View</button>
185
- </div>
186
- <div class="tooltip" id="tooltip"></div>
187
  </div>
188
  </div>
189
 
190
  <script>
191
  // SDR ์Šคํ…Œ์ด์…˜ ๋ฐ์ดํ„ฐ
192
- const sdrStations = [
193
- // ์œ ๋Ÿฝ
194
  {
195
  name: "Twente WebSDR",
196
- url: "websdr.ewi.utwente.nl:8901",
197
  location: [52.2389, 6.8343],
198
- frequency: "0-29.160 MHz",
199
- range: 200,
200
- active: true,
201
- region: "Europe"
202
  },
203
  {
204
  name: "TU Delft WebSDR",
205
- url: "websdr.tudelft.nl:8901",
206
  location: [51.9981, 4.3731],
207
- frequency: "0-29.160 MHz",
208
- range: 180,
209
- active: true,
210
- region: "Europe"
211
  },
212
- // ๋ถ๋ฏธ
213
  {
214
  name: "W6YXP WebSDR",
215
- url: "w6yxp.stanford.edu",
216
  location: [37.4275, -122.1697],
217
- frequency: "0-30 MHz",
218
- range: 200,
219
- active: true,
220
- region: "North America"
221
- },
222
- {
223
- name: "K3FEF WebSDR",
224
- url: "k3fef.com:8901",
225
- location: [40.5697, -75.9363],
226
- frequency: "0-30 MHz",
227
- range: 180,
228
- active: true,
229
- region: "North America"
230
  },
231
- // ์•„์‹œ์•„
232
  {
233
  name: "Tokyo WebSDR",
234
- url: "tokyo.websdr.org",
235
  location: [35.6762, 139.6503],
236
- frequency: "0-30 MHz",
237
- range: 200,
238
- active: true,
239
- region: "Asia"
240
- },
241
- {
242
- name: "Seoul WebSDR",
243
- url: "seoul.websdr.org",
244
- location: [37.5665, 126.9780],
245
- frequency: "0-30 MHz",
246
- range: 180,
247
- active: true,
248
- region: "Asia"
249
- },
250
- // ์˜ค์„ธ์•„๋‹ˆ์•„
251
- {
252
- name: "Sydney WebSDR",
253
- url: "sydney.websdr.org",
254
- location: [-33.8688, 151.2093],
255
- frequency: "0-30 MHz",
256
- range: 200,
257
- active: true,
258
- region: "Oceania"
259
- },
260
- // ๋‚จ๋ฏธ
261
- {
262
- name: "Sรฃo Paulo WebSDR",
263
- url: "saopaulo.websdr.org",
264
- location: [-23.5505, -46.6333],
265
- frequency: "0-30 MHz",
266
- range: 180,
267
- active: true,
268
- region: "South America"
269
  }
270
  ];
271
 
272
- class GlobalRadarSystem {
273
  constructor() {
274
  this.canvas = document.getElementById('map');
275
  this.ctx = this.canvas.getContext('2d');
276
- this.targets = new Set();
277
- this.zoom = 1;
278
- this.offset = { x: 0, y: 0 };
279
- this.isDragging = false;
280
- this.lastMousePos = { x: 0, y: 0 };
281
- this.tooltip = document.getElementById('tooltip');
282
-
283
  this.setupCanvas();
284
- this.setupControls();
285
- this.setupEventListeners();
286
- this.renderReceivers();
287
- this.loadWorldMap();
288
- this.startTracking();
289
  }
290
 
291
  setupCanvas() {
292
- const resizeCanvas = () => {
 
293
  const container = this.canvas.parentElement;
294
  this.canvas.width = container.offsetWidth;
295
  this.canvas.height = container.offsetHeight;
296
- this.draw();
297
  };
298
 
299
- resizeCanvas();
300
- window.addEventListener('resize', resizeCanvas);
301
- }
302
-
303
- setupControls() {
304
- document.getElementById('zoomIn').addEventListener('click', () => {
305
- this.zoom *= 1.2;
306
- this.draw();
307
- });
308
-
309
- document.getElementById('zoomOut').addEventListener('click', () => {
310
- this.zoom /= 1.2;
311
- this.draw();
312
- });
313
-
314
- document.getElementById('resetView').addEventListener('click', () => {
315
- this.zoom = 1;
316
- this.offset = { x: 0, y: 0 };
317
- this.draw();
318
- });
319
- }
320
-
321
- setupEventListeners() {
322
- this.canvas.addEventListener('mousedown', (e) => {
323
- this.isDragging = true;
324
- this.lastMousePos = {
325
- x: e.clientX,
326
- y: e.clientY
327
- };
328
- });
329
-
330
- this.canvas.addEventListener('mousemove', (e) => {
331
- if (this.isDragging) {
332
- this.offset.x += (e.clientX - this.lastMousePos.x) / this.zoom;
333
- this.offset.y += (e.clientY - this.lastMousePos.y) / this.zoom;
334
- this.lastMousePos = {
335
- x: e.clientX,
336
- y: e.clientY
337
- };
338
- this.draw();
339
- }
340
-
341
- // ํˆดํŒ ์—…๋ฐ์ดํŠธ
342
- const rect = this.canvas.getBoundingClientRect();
343
- const mouseX = e.clientX - rect.left;
344
- const mouseY = e.clientY - rect.top;
345
- this.showTooltip(mouseX, mouseY);
346
  });
347
-
348
- this.canvas.addEventListener('mouseup', () => {
349
- this.isDragging = false;
350
- });
351
-
352
- this.canvas.addEventListener('mouseleave', () => {
353
- this.isDragging = false;
354
- this.tooltip.style.display = 'none';
355
- });
356
-
357
- this.canvas.addEventListener('wheel', (e) => {
358
- e.preventDefault();
359
- const zoomFactor = e.deltaY > 0 ? 0.9 : 1.1;
360
- this.zoom *= zoomFactor;
361
- this.draw();
362
- });
363
- }
364
-
365
- showTooltip(mouseX, mouseY) {
366
- // ์Šคํ…Œ์ด์…˜๊ณผ ํƒ€๊ฒŸ์— ๋Œ€ํ•œ ํˆดํŒ
367
- let tooltipContent = null;
368
-
369
- // ์Šคํ…Œ์ด์…˜ ํ™•์ธ
370
- sdrStations.forEach(station => {
371
- const pos = this.latLongToXY(station.location[0], station.location[1]);
372
- const distance = Math.hypot(pos.x - mouseX, pos.y - mouseY);
373
- if (distance < 10) {
374
- tooltipContent = `
375
- ${station.name}<br>
376
- Frequency: ${station.frequency}<br>
377
- Location: ${station.location.join(', ')}<br>
378
- Range: ${station.range}km
379
- `;
380
- }
381
- });
382
-
383
- // ํƒ€๊ฒŸ ํ™•์ธ
384
- this.targets.forEach(target => {
385
- const pos = this.latLongToXY(target.position.lat, target.position.lon);
386
- const distance = Math.hypot(pos.x - mouseX, pos.y - mouseY);
387
- if (distance < 10) {
388
- tooltipContent = `
389
- ID: ${target.id}<br>
390
- Type: ${target.type}<br>
391
- Speed: ${target.speed.toFixed(0)}kts<br>
392
- Altitude: ${target.altitude.toFixed(0)}ft<br>
393
- Position: ${target.position.lat.toFixed(2)}ยฐ, ${target.position.lon.toFixed(2)}ยฐ
394
- `;
395
- }
396
- });
397
-
398
- if (tooltipContent) {
399
- this.tooltip.style.display = 'block';
400
- this.tooltip.style.left = (mouseX + 10) + 'px';
401
- this.tooltip.style.top = (mouseY + 10) + 'px';
402
- this.tooltip.innerHTML = tooltipContent;
403
- } else {
404
- this.tooltip.style.display = 'none';
405
- }
406
  }
407
 
408
- loadWorldMap() {
409
- this.worldGrid = {
410
- latLines: [],
411
- lonLines: []
412
- };
413
-
414
- for(let lat = -75; lat <= 75; lat += 15) {
415
- this.worldGrid.latLines.push(lat);
416
- }
417
-
418
- for(let lon = -180; lon <= 180; lon += 30) {
419
- this.worldGrid.lonLines.push(lon);
420
- }
421
  }
422
 
423
  latLongToXY(lat, lon) {
424
  const mapWidth = this.canvas.width;
425
  const mapHeight = this.canvas.height;
426
 
427
- // ๋ฉ”๋ฅด์นดํ† ๋ฅด ํˆฌ์˜ ๋ณ€ํ™˜
428
- const x = (lon + 180) * (mapWidth / 360) * this.zoom + this.offset.x;
429
  const latRad = lat * Math.PI / 180;
430
  const mercN = Math.log(Math.tan((Math.PI / 4) + (latRad / 2)));
431
- const y = (mapHeight / 2 - (mapWidth * mercN / (2 * Math.PI))) * this.zoom + this.offset.y;
432
 
433
  return {x, y};
434
  }
435
 
436
- draw() {
437
- this.drawWorldMap();
438
- this.drawStations();
439
- this.drawTargets();
440
- }
441
-
442
- drawWorldMap() {
443
- // ๋ฐฐ๊ฒฝ
444
  this.ctx.fillStyle = '#111';
445
  this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
446
 
447
- // ๊ทธ๋ฆฌ๋“œ
448
  this.ctx.strokeStyle = '#1a1a1a';
449
  this.ctx.lineWidth = 1;
450
 
451
- // ์œ„๋„์„ 
452
- this.worldGrid.latLines.forEach(lat => {
453
- const start = this.latLongToXY(lat, -180);
454
- const end = this.latLongToXY(lat, 180);
455
  this.ctx.beginPath();
456
  this.ctx.moveTo(start.x, start.y);
457
  this.ctx.lineTo(end.x, end.y);
458
  this.ctx.stroke();
459
-
460
- // ์œ„๋„ ๋ ˆ์ด๋ธ”
461
- this.ctx.fillStyle = '#444';
462
- this.ctx.font = '10px monospace';
463
- this.ctx.fillText(`${lat}ยฐ`, end.x + 5, end.y);
464
- });
465
 
466
- // ๊ฒฝ๋„์„ 
467
- this.worldGrid.lonLines.forEach(lon => {
468
- const start = this.latLongToXY(-75, lon);
469
- const end = this.latLongToXY(75, lon);
470
  this.ctx.beginPath();
471
  this.ctx.moveTo(start.x, start.y);
472
  this.ctx.lineTo(end.x, end.y);
473
  this.ctx.stroke();
474
-
475
- // ๊ฒฝ๋„ ๋ ˆ์ด๋ธ”
476
- this.ctx.fillStyle = '#444';
477
- this.ctx.font = '10px monospace';
478
- this.ctx.fillText(`${lon}ยฐ`, start.x - 15, this.canvas.height - 5);
479
- });
480
- }
481
 
482
- drawStations() {
483
- sdrStations.forEach(station => {
484
  const pos = this.latLongToXY(station.location[0], station.location[1]);
485
-
486
- // ์ปค๋ฒ„๋ฆฌ์ง€ ๋ฒ”์œ„
487
- this.ctx.beginPath();
488
- this.ctx.arc(pos.x, pos.y, (station.range / 10) * this.zoom, 0, Math.PI * 2);
489
- this.ctx.strokeStyle = `rgba(0,255,0,${station.active ? 0.2 : 0.1})`;
490
- this.ctx.stroke();
491
-
492
  // ์Šคํ…Œ์ด์…˜ ํฌ์ธํŠธ
493
  this.ctx.beginPath();
494
- this.ctx.arc(pos.x, pos.y, 4, 0, Math.PI * 2);
495
- this.ctx.fillStyle = station.active ? '#0f0' : '#f00';
496
  this.ctx.fill();
497
 
498
- // ์Šคํ…Œ์ด์…˜ ๋ ˆ์ด๋ธ”
499
  this.ctx.fillStyle = '#0f0';
500
- this.ctx.font = `${10 * this.zoom}px monospace`;
501
- this.ctx.fillText(station.name, pos.x + 10, pos.y + 4);
502
- });
503
- }
504
-
505
- drawTargets() {
506
- this.targets.forEach(target => {
507
- const pos = this.latLongToXY(target.position.lat, target.position.lon);
508
-
509
- // ํƒ€๊ฒŸ๊ณผ ์Šคํ…Œ์ด์…˜ ์—ฐ๊ฒฐ์„ 
510
- sdrStations.forEach(station => {
511
- if(station.active) {
512
- const stationPos = this.latLongToXY(station.location[0], station.location[1]);
513
- const distance = Math.hypot(pos.x - stationPos.x, pos.y - stationPos.y);
514
- if(distance < station.range * this.zoom / 5) {
515
- this.ctx.beginPath();
516
- this.ctx.strokeStyle = `rgba(0,255,0,${target.signalStrength * 0.3})`;
517
- this.ctx.moveTo(stationPos.x, stationPos.y);
518
- this.ctx.lineTo(pos.x, pos.y);
519
- this.ctx.stroke();
520
- }
521
- }
522
- });
523
-
524
- // ํƒ€๊ฒŸ ์•„์ด์ฝ˜
525
- const size = 3 * this.zoom;
526
- if(target.type === 'aircraft') {
527
- // ํ•ญ๊ณต๊ธฐ ์•„์ด์ฝ˜
528
- this.ctx.fillStyle = '#ff0';
529
- this.ctx.beginPath();
530
- this.ctx.moveTo(pos.x, pos.y - size);
531
- this.ctx.lineTo(pos.x + size, pos.y + size);
532
- this.ctx.lineTo(pos.x - size, pos.y + size);
533
- this.ctx.closePath();
534
- this.ctx.fill();
535
- } else {
536
- // ์ฐจ๋Ÿ‰ ์•„์ด์ฝ˜
537
- this.ctx.fillStyle = '#0ff';
538
- this.ctx.fillRect(pos.x - size, pos.y - size, size * 2, size * 2);
539
- }
540
-
541
- // ํƒ€๊ฒŸ ์ •๋ณด ๋ ˆ์ด๋ธ”
542
- this.ctx.fillStyle = '#666';
543
- this.ctx.font = `${10 * this.zoom}px monospace`;
544
- this.ctx.fillText(
545
- `${target.id} โ€ข ${target.speed.toFixed(0)}kts โ€ข ${target.altitude.toFixed(0)}ft`,
546
- pos.x + 10,
547
- pos.y + 4
548
- );
549
  });
550
  }
551
-
552
- updateDetections() {
553
- const detections = document.getElementById('detections');
554
- detections.innerHTML = Array.from(this.targets)
555
- .map(target => `
556
- <div class="detection">
557
- ${target.type === 'aircraft' ? 'โœˆ๏ธ' : '๐Ÿš—'}
558
- ${target.id}
559
- ${target.speed.toFixed(0)}kts
560
- ${target.type === 'aircraft' ? `${target.altitude.toFixed(0)}ft` : ''}
561
- Signal: ${(target.signalStrength * 100).toFixed(0)}%
562
- Position: ${target.position.lat.toFixed(2)}ยฐ, ${target.position.lon.toFixed(2)}ยฐ
563
- </div>
564
- `).join('');
565
- }
566
-
567
- updateSignalStrengths() {
568
- sdrStations.forEach(station => {
569
- const bar = document.querySelector(`#rx-${station.url.split(':')[0]} .signal-bar`);
570
- if(bar) {
571
- const strength = 40 + Math.random() * 60;
572
- bar.style.width = `${strength}%`;
573
- }
574
- });
575
- }
576
-
577
- generateTarget() {
578
- return {
579
- type: Math.random() > 0.7 ? 'aircraft' : 'vehicle',
580
- position: {
581
- lat: -75 + Math.random() * 150,
582
- lon: -180 + Math.random() * 360
583
- },
584
- speed: Math.random() * 500 + 200,
585
- altitude: Math.random() * 35000 + 5000,
586
- heading: Math.random() * 360,
587
- id: Math.random().toString(36).substr(2, 6).toUpperCase(),
588
- signalStrength: Math.random()
589
- };
590
- }
591
-
592
- startTracking() {
593
- setInterval(() => {
594
- // ํƒ€๊ฒŸ ์ถ”๊ฐ€/์ œ๊ฑฐ
595
- if(Math.random() < 0.1 && this.targets.size < 15) {
596
- this.targets.add(this.generateTarget());
597
- }
598
- if(Math.random() < 0.1 && this.targets.size > 0) {
599
- this.targets.delete(Array.from(this.targets)[0]);
600
- }
601
-
602
- // ๊ธฐ์กด ํƒ€๊ฒŸ ์—…๋ฐ์ดํŠธ
603
- this.targets.forEach(target => {
604
- const speed = target.speed / 3600;
605
- const radians = target.heading * Math.PI / 180;
606
-
607
- target.position.lon += Math.sin(radians) * speed;
608
- target.position.lat += Math.cos(radians) * speed;
609
-
610
- // ํ™”๋ฉด ๊ฒฝ๊ณ„ ์ฒ˜๋ฆฌ
611
- if(target.position.lon > 180) target.position.lon -= 360;
612
- if(target.position.lon < -180) target.position.lon += 360;
613
- if(target.position.lat > 75) target.position.lat = 75;
614
- if(target.position.lat < -75) target.position.lat = -75;
615
-
616
- // ๊ฐ€๋” ๋ฐฉํ–ฅ ๋ณ€๊ฒฝ
617
- if(Math.random() < 0.05) {
618
- target.heading += (Math.random() - 0.5) * 30;
619
- target.heading = (target.heading + 360) % 360;
620
- }
621
-
622
- // ์‹ ํ˜ธ ๊ฐ•๋„ ๋ณ€๋™
623
- target.signalStrength = Math.max(0.1, Math.min(1, target.signalStrength + (Math.random() - 0.5) * 0.1));
624
- });
625
-
626
- this.draw();
627
- this.updateDetections();
628
- this.updateSignalStrengths();
629
- }, 100);
630
- }
631
  }
632
 
633
- // Initialize global radar system
634
- const radar = new GlobalRadarSystem();
 
 
 
 
2
  <html>
3
  <head>
4
  <meta charset="UTF-8">
5
+ <title>Basic SDR Network Monitor</title>
6
  <style>
7
  body {
8
  margin: 0;
 
10
  background: #000;
11
  color: #0f0;
12
  font-family: monospace;
 
13
  }
14
 
15
  .container {
 
30
  background: #111;
31
  border-radius: 8px;
32
  position: relative;
 
33
  }
34
 
35
  .receiver {
 
37
  padding: 10px;
38
  background: #1a1a1a;
39
  border-radius: 4px;
 
 
 
 
 
40
  }
41
 
42
  .status {
 
50
  height: 8px;
51
  border-radius: 50%;
52
  margin-right: 8px;
 
 
 
53
  background: #0f0;
 
 
 
 
 
 
 
 
 
 
 
 
54
  }
55
 
56
+ canvas {
57
  width: 100%;
58
  height: 100%;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  }
60
  </style>
61
  </head>
62
  <body>
63
  <div class="container">
64
  <div class="sidebar">
65
+ <h3>SDR Receivers</h3>
66
  <div id="receivers"></div>
 
 
 
67
  </div>
 
68
  <div class="map-container">
69
  <canvas id="map"></canvas>
 
 
 
 
 
 
70
  </div>
71
  </div>
72
 
73
  <script>
74
  // SDR ์Šคํ…Œ์ด์…˜ ๋ฐ์ดํ„ฐ
75
+ const stations = [
 
76
  {
77
  name: "Twente WebSDR",
 
78
  location: [52.2389, 6.8343],
79
+ active: true
 
 
 
80
  },
81
  {
82
  name: "TU Delft WebSDR",
 
83
  location: [51.9981, 4.3731],
84
+ active: true
 
 
 
85
  },
 
86
  {
87
  name: "W6YXP WebSDR",
 
88
  location: [37.4275, -122.1697],
89
+ active: true
 
 
 
 
 
 
 
 
 
 
 
 
90
  },
 
91
  {
92
  name: "Tokyo WebSDR",
 
93
  location: [35.6762, 139.6503],
94
+ active: true
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
  }
96
  ];
97
 
98
+ class SDRMonitor {
99
  constructor() {
100
  this.canvas = document.getElementById('map');
101
  this.ctx = this.canvas.getContext('2d');
 
 
 
 
 
 
 
102
  this.setupCanvas();
103
+ this.renderStations();
104
+ this.drawMap();
 
 
 
105
  }
106
 
107
  setupCanvas() {
108
+ // ์บ”๋ฒ„์Šค ํฌ๊ธฐ ์„ค์ •
109
+ const updateSize = () => {
110
  const container = this.canvas.parentElement;
111
  this.canvas.width = container.offsetWidth;
112
  this.canvas.height = container.offsetHeight;
 
113
  };
114
 
115
+ updateSize();
116
+ window.addEventListener('resize', () => {
117
+ updateSize();
118
+ this.drawMap();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
119
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
120
  }
121
 
122
+ renderStations() {
123
+ const container = document.getElementById('receivers');
124
+ container.innerHTML = stations.map(station => `
125
+ <div class="receiver">
126
+ <div class="status">
127
+ <div class="led"></div>
128
+ <strong>${station.name}</strong>
129
+ </div>
130
+ <div>Location: ${station.location.join(', ')}</div>
131
+ </div>
132
+ `).join('');
 
 
133
  }
134
 
135
  latLongToXY(lat, lon) {
136
  const mapWidth = this.canvas.width;
137
  const mapHeight = this.canvas.height;
138
 
139
+ const x = (lon + 180) * (mapWidth / 360);
 
140
  const latRad = lat * Math.PI / 180;
141
  const mercN = Math.log(Math.tan((Math.PI / 4) + (latRad / 2)));
142
+ const y = (mapHeight / 2) - (mapWidth * mercN / (2 * Math.PI));
143
 
144
  return {x, y};
145
  }
146
 
147
+ drawMap() {
148
+ // ๋ฐฐ๊ฒฝ ๊ทธ๋ฆฌ๊ธฐ
 
 
 
 
 
 
149
  this.ctx.fillStyle = '#111';
150
  this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
151
 
152
+ // ๊ทธ๋ฆฌ๋“œ ๊ทธ๋ฆฌ๊ธฐ
153
  this.ctx.strokeStyle = '#1a1a1a';
154
  this.ctx.lineWidth = 1;
155
 
156
+ // ๊ฒฝ๋„์„  (30๋„ ๊ฐ„๊ฒฉ)
157
+ for(let lon = -180; lon <= 180; lon += 30) {
158
+ const start = this.latLongToXY(-80, lon);
159
+ const end = this.latLongToXY(80, lon);
160
  this.ctx.beginPath();
161
  this.ctx.moveTo(start.x, start.y);
162
  this.ctx.lineTo(end.x, end.y);
163
  this.ctx.stroke();
164
+ }
 
 
 
 
 
165
 
166
+ // ์œ„๋„์„  (20๋„ ๊ฐ„๊ฒฉ)
167
+ for(let lat = -80; lat <= 80; lat += 20) {
168
+ const start = this.latLongToXY(lat, -180);
169
+ const end = this.latLongToXY(lat, 180);
170
  this.ctx.beginPath();
171
  this.ctx.moveTo(start.x, start.y);
172
  this.ctx.lineTo(end.x, end.y);
173
  this.ctx.stroke();
174
+ }
 
 
 
 
 
 
175
 
176
+ // SDR ์Šคํ…Œ์ด์…˜ ๊ทธ๋ฆฌ๊ธฐ
177
+ stations.forEach(station => {
178
  const pos = this.latLongToXY(station.location[0], station.location[1]);
179
+
 
 
 
 
 
 
180
  // ์Šคํ…Œ์ด์…˜ ํฌ์ธํŠธ
181
  this.ctx.beginPath();
182
+ this.ctx.arc(pos.x, pos.y, 5, 0, Math.PI * 2);
183
+ this.ctx.fillStyle = '#0f0';
184
  this.ctx.fill();
185
 
186
+ // ์Šคํ…Œ์ด์…˜ ์ด๋ฆ„
187
  this.ctx.fillStyle = '#0f0';
188
+ this.ctx.font = '12px monospace';
189
+ this.ctx.fillText(station.name, pos.x + 10, pos.y + 5);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
190
  });
191
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
192
  }
193
 
194
+ // ๋ชจ๋‹ˆํ„ฐ ์‹œ์Šคํ…œ ์ดˆ๊ธฐํ™”
195
+ const monitor = new SDRMonitor();
196
+ </script>
197
+ </body>
198
+ </html>