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

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +277 -108
index.html CHANGED
@@ -17,22 +17,33 @@
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 {
@@ -65,9 +76,9 @@
65
  }
66
 
67
  #map {
68
- background: #111;
69
- border-radius: 8px;
70
- height: calc(100vh - 40px);
71
  }
72
 
73
  .signal-strength {
@@ -89,6 +100,69 @@
89
  margin: 5px 0;
90
  font-size: 12px;
91
  border-left: 2px solid #0f0;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92
  }
93
  </style>
94
  </head>
@@ -102,11 +176,19 @@
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
  {
@@ -192,151 +274,230 @@
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
  }
@@ -345,12 +506,12 @@
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);
@@ -360,15 +521,26 @@
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,
@@ -377,18 +549,6 @@
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)
@@ -414,6 +574,21 @@
414
  });
415
  }
416
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
417
  startTracking() {
418
  setInterval(() => {
419
  // ํƒ€๊ฒŸ ์ถ”๊ฐ€/์ œ๊ฑฐ
@@ -424,10 +599,9 @@
424
  this.targets.delete(Array.from(this.targets)[0]);
425
  }
426
 
427
- // ๊ธฐ์กด ํƒ€๊ฒŸ ์—…๋ฐ์ดํŠธ (์›€์ง์ž„ ์‹œ๋ฎฌ๋ ˆ์ด์…˜)
428
  this.targets.forEach(target => {
429
- // ํ˜„์žฌ ๋ฐฉํ–ฅ์œผ๋กœ ์ด๋™
430
- const speed = target.speed / 3600; // kts to degrees per second (approximate)
431
  const radians = target.heading * Math.PI / 180;
432
 
433
  target.position.lon += Math.sin(radians) * speed;
@@ -449,9 +623,7 @@
449
  target.signalStrength = Math.max(0.1, Math.min(1, target.signalStrength + (Math.random() - 0.5) * 0.1));
450
  });
451
 
452
- this.drawWorldMap();
453
- this.drawStations();
454
- this.drawTargets();
455
  this.updateDetections();
456
  this.updateSignalStrengths();
457
  }, 100);
@@ -459,7 +631,4 @@
459
  }
460
 
461
  // Initialize global radar system
462
- const radar = new GlobalRadarSystem();
463
- </script>
464
- </body>
465
- </html>
 
17
  display: grid;
18
  grid-template-columns: 300px 1fr;
19
  gap: 20px;
20
+ height: calc(100vh - 40px);
21
  }
22
 
23
  .sidebar {
24
  background: #111;
25
  padding: 15px;
26
  border-radius: 8px;
 
27
  overflow-y: auto;
28
  }
29
 
30
+ .map-container {
31
+ background: #111;
32
+ border-radius: 8px;
33
+ position: relative;
34
+ overflow: hidden;
35
+ }
36
+
37
  .receiver {
38
  margin: 10px 0;
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 {
 
76
  }
77
 
78
  #map {
79
+ width: 100%;
80
+ height: 100%;
81
+ cursor: move;
82
  }
83
 
84
  .signal-strength {
 
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>
 
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
  {
 
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
  }
 
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);
 
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,
 
549
  });
550
  }
551
 
 
 
 
 
 
 
 
 
 
 
 
 
552
  updateDetections() {
553
  const detections = document.getElementById('detections');
554
  detections.innerHTML = Array.from(this.targets)
 
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
  // ํƒ€๊ฒŸ ์ถ”๊ฐ€/์ œ๊ฑฐ
 
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;
 
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);
 
631
  }
632
 
633
  // Initialize global radar system
634
+ const radar = new GlobalRadarSystem();