204204 box-shadow: 0 2px 5px rgba(0,0,0,0.2);
205205 z-index: 1000;
206206 }
207+
208+ /* Enhanced popup styling */
209+ .leaflet-popup-content-wrapper {
210+ border-radius: 8px;
211+ box-shadow: 0 4px 12px rgba(0,0,0,0.3);
212+ }
213+
214+ .leaflet-popup-content {
215+ margin: 16px 20px;
216+ line-height: 1.6;
217+ font-size: 14px;
218+ min-width: 280px;
219+ max-width: 350px;
220+ }
221+
222+ .aircraft-popup {
223+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
224+ }
225+
226+ .aircraft-popup .aircraft-title {
227+ font-size: 16px;
228+ font-weight: bold;
229+ color: #2c3e50;
230+ margin-bottom: 8px;
231+ border-bottom: 2px solid #3498db;
232+ padding-bottom: 4px;
233+ }
234+
235+ .aircraft-popup .aircraft-details {
236+ display: grid;
237+ gap: 4px;
238+ margin-bottom: 10px;
239+ }
240+
241+ .aircraft-popup .aircraft-link {
242+ display: inline-block;
243+ background: #3498db;
244+ color: white;
245+ padding: 8px 16px;
246+ border-radius: 4px;
247+ text-decoration: none;
248+ font-weight: 500;
249+ text-align: center;
250+ margin-top: 8px;
251+ transition: background-color 0.2s;
252+ }
253+
254+ .aircraft-popup .aircraft-link:hover {
255+ background: #2980b9;
256+ color: white;
257+ text-decoration: none;
258+ }
207259 </style>
208260</head>
209261<body>
517569 }).addTo(aircraftLayer);
518570
519571 marker.bindPopup(`
520- <strong>${circle.callsign}</strong><br>
521- Circling: ${circle.turns.toFixed(1)} turns<br>
522- Radius: ${circle.radius.toFixed(1)} km<br>
523- ${circle.current_alt ? `Altitude: ${circle.current_alt.toLocaleString()} ft<br>` : ''}
524- ${circle.current_speed ? `Speed: ${circle.current_speed} kts` : ''}
572+ <div class="aircraft-popup">
573+ <div class="aircraft-title">π΄ ${circle.callsign}</div>
574+ <div class="aircraft-details">
575+ <div><strong>Pattern:</strong> Circling (${circle.turns.toFixed(1)} turns)</div>
576+ <div><strong>Radius:</strong> ${circle.radius.toFixed(1)} km</div>
577+ ${circle.current_alt ? `<div><strong>Altitude:</strong> ${circle.current_alt.toLocaleString()} ft</div>` : ''}
578+ ${circle.current_speed ? `<div><strong>Speed:</strong> ${circle.current_speed} kts</div>` : ''}
579+ <div><strong>Aircraft ID:</strong> ${circle.hex_id}</div>
580+ ${circle.type ? `<div><strong>Type:</strong> ${circle.type}</div>` : ''}
581+ </div>
582+ ${circle.tar1090_radar_url ? `<a href="${circle.tar1090_radar_url}" target="_blank" class="aircraft-link">π‘ View on Radar</a>` : ''}
583+ </div>
525584 `);
526585
527586 visibleAircraftCount++;
577636 }).addTo(aircraftLayer);
578637
579638 marker.bindPopup(`
580- <strong>${grid.callsign}</strong><br>
581- Pattern: ${grid.pattern_type}<br>
582- Legs: ${grid.num_legs}<br>
583- Coverage: ${grid.coverage_area.toFixed(1)} kmΒ²<br>
584- ${grid.current_alt ? `Altitude: ${grid.current_alt.toLocaleString()} ft<br>` : ''}
585- ${grid.current_speed ? `Speed: ${grid.current_speed} kts` : ''}
639+ <div class="aircraft-popup">
640+ <div class="aircraft-title">π’ ${grid.callsign}</div>
641+ <div class="aircraft-details">
642+ <div><strong>Pattern:</strong> ${grid.pattern_type}</div>
643+ <div><strong>Grid Legs:</strong> ${grid.num_legs}</div>
644+ <div><strong>Coverage:</strong> ${grid.coverage_area.toFixed(1)} kmΒ²</div>
645+ ${grid.current_alt ? `<div><strong>Altitude:</strong> ${grid.current_alt.toLocaleString()} ft</div>` : ''}
646+ ${grid.current_speed ? `<div><strong>Speed:</strong> ${grid.current_speed} kts</div>` : ''}
647+ <div><strong>Aircraft ID:</strong> ${grid.hex_id}</div>
648+ ${grid.type ? `<div><strong>Type:</strong> ${grid.type}</div>` : ''}
649+ </div>
650+ ${grid.tar1090_radar_url ? `<a href="${grid.tar1090_radar_url}" target="_blank" class="aircraft-link">π‘ View on Radar</a>` : ''}
651+ </div>
586652 `);
587653
588654 visibleAircraftCount++;
625691 }).addTo(aircraftLayer);
626692
627693 marker.bindPopup(`
628- <strong>${aircraft.callsign}</strong><br>
629- Type: ${aircraft.type || 'Unknown'}<br>
630- ${aircraft.current_alt ? `Altitude: ${aircraft.current_alt.toLocaleString()} ft<br>` : ''}
631- ${aircraft.current_speed ? `Speed: ${aircraft.current_speed} kts` : ''}
694+ <div class="aircraft-popup">
695+ <div class="aircraft-title">βοΈ ${aircraft.callsign}</div>
696+ <div class="aircraft-details">
697+ <div><strong>Type:</strong> ${aircraft.type || 'Unknown'}</div>
698+ ${aircraft.current_alt ? `<div><strong>Altitude:</strong> ${aircraft.current_alt.toLocaleString()} ft</div>` : ''}
699+ ${aircraft.current_speed ? `<div><strong>Speed:</strong> ${aircraft.current_speed} kts</div>` : ''}
700+ <div><strong>Aircraft ID:</strong> ${aircraft.hex_id}</div>
701+ ${aircraft.category ? `<div><strong>Category:</strong> ${aircraft.category}</div>` : ''}
702+ </div>
703+ ${aircraft.tar1090_radar_url ? `<a href="${aircraft.tar1090_radar_url}" target="_blank" class="aircraft-link">π‘ View on Radar</a>` : ''}
704+ </div>
632705 `);
633706
634707 visibleAircraftCount++;
@@ -1878,8 +1951,11 @@ def __init__(self, server_url: str, update_interval: int = 5):
18781951 self .active_grids : Set [str ] = set () # Track aircraft currently in grid patterns
18791952 self .circle_start_times : Dict [str , float ] = {} # Track when circling started
18801953 self .grid_start_times : Dict [str , float ] = {} # Track when grid pattern started
1881- self .log_file = Path ("circle_detections.csv" )
1882- self .grid_log_file = Path ("grid_detections.csv" )
1954+ # Use data directory for persistent storage
1955+ self .data_dir = Path ("/app/data" )
1956+ self .data_dir .mkdir (parents = True , exist_ok = True )
1957+ self .log_file = self .data_dir / "circle_detections.csv"
1958+ self .grid_log_file = self .data_dir / "grid_detections.csv"
18831959 self .tar1090_base_url = "https://radar.hallgren.net/map"
18841960
18851961 # Display settings
@@ -2438,13 +2514,17 @@ def get_pattern_data_json(self, include_all_aircraft=True, max_track_points=50):
24382514
24392515 # Add circling aircraft
24402516 for aircraft , detection in self .get_circling_aircraft ():
2517+ # Generate TAR1090 radar URL for circling aircraft
2518+ tar1090_radar_url = f"{ self .tar1090_base_url } /?icao={ aircraft .hex_id } &zoom=12"
2519+
24412520 circle_data = {
24422521 'hex_id' : aircraft .hex_id ,
24432522 'callsign' : aircraft .callsign ,
24442523 'center_lat' : detection .center_lat ,
24452524 'center_lon' : detection .center_lon ,
24462525 'radius' : detection .radius ,
24472526 'turns' : detection .turns ,
2527+ 'tar1090_radar_url' : tar1090_radar_url ,
24482528 'path' : [
24492529 {'lat' : p .lat , 'lon' : p .lon , 'alt' : p .altitude , 'time' : p .timestamp }
24502530 for p in aircraft .path
@@ -2458,6 +2538,9 @@ def get_pattern_data_json(self, include_all_aircraft=True, max_track_points=50):
24582538
24592539 # Add grid aircraft
24602540 for aircraft , detection in self .get_grid_aircraft ():
2541+ # Generate TAR1090 radar URL for grid aircraft
2542+ tar1090_radar_url = f"{ self .tar1090_base_url } /?icao={ aircraft .hex_id } &zoom=12"
2543+
24612544 grid_data = {
24622545 'hex_id' : aircraft .hex_id ,
24632546 'callsign' : aircraft .callsign ,
@@ -2467,6 +2550,7 @@ def get_pattern_data_json(self, include_all_aircraft=True, max_track_points=50):
24672550 'grid_bearing' : detection .grid_bearing ,
24682551 'line_spacing' : detection .line_spacing ,
24692552 'num_legs' : detection .num_legs ,
2553+ 'tar1090_radar_url' : tar1090_radar_url ,
24702554 'coverage_area' : detection .coverage_area ,
24712555 'path' : [
24722556 {'lat' : p .lat , 'lon' : p .lon , 'alt' : p .altitude , 'time' : p .timestamp }
@@ -2494,6 +2578,9 @@ def get_pattern_data_json(self, include_all_aircraft=True, max_track_points=50):
24942578 # Limit track points for performance
24952579 track_points = aircraft .path [- max_track_points :] if len (aircraft .path ) > max_track_points else aircraft .path
24962580
2581+ # Generate TAR1090 radar URL for live aircraft
2582+ tar1090_radar_url = f"{ self .tar1090_base_url } /?icao={ aircraft .hex_id } &zoom=12"
2583+
24972584 aircraft_data = {
24982585 'hex_id' : aircraft .hex_id ,
24992586 'callsign' : aircraft .callsign ,
@@ -2505,6 +2592,7 @@ def get_pattern_data_json(self, include_all_aircraft=True, max_track_points=50):
25052592 'current_speed' : aircraft .path [- 1 ].speed if aircraft .path else None ,
25062593 'type' : aircraft .type ,
25072594 'category' : aircraft .category ,
2595+ 'tar1090_radar_url' : tar1090_radar_url ,
25082596 'in_pattern' : False
25092597 }
25102598 data ['all_aircraft' ].append (aircraft_data )
@@ -2776,7 +2864,8 @@ def main():
27762864
27772865 # Handle log-related commands first
27782866 if args .show_log :
2779- log_file = Path ("circle_detections.csv" )
2867+ data_dir = Path ("/app/data" )
2868+ log_file = data_dir / "circle_detections.csv"
27802869 if not log_file .exists ():
27812870 print ("π No log file found. Start monitoring to create one." )
27822871 sys .exit (0 )
@@ -2808,12 +2897,22 @@ def main():
28082897 sys .exit (0 )
28092898
28102899 if args .clear_log :
2811- log_file = Path ("circle_detections.csv" )
2900+ data_dir = Path ("/app/data" )
2901+ log_file = data_dir / "circle_detections.csv"
2902+ grid_log_file = data_dir / "grid_detections.csv"
2903+
2904+ cleared = False
28122905 if log_file .exists ():
28132906 log_file .unlink ()
2814- print ("β
Log file cleared." )
2815- else :
2816- print ("π No log file to clear." )
2907+ print ("β
Circle log file cleared." )
2908+ cleared = True
2909+ if grid_log_file .exists ():
2910+ grid_log_file .unlink ()
2911+ print ("β
Grid log file cleared." )
2912+ cleared = True
2913+
2914+ if not cleared :
2915+ print ("π No log files to clear." )
28172916 sys .exit (0 )
28182917
28192918 # Create monitor with custom settings
0 commit comments