Spaces:
Running
Running
import gradio as gr | |
import folium | |
from folium import plugins | |
import requests | |
import pandas as pd | |
from datetime import datetime | |
import time | |
import branca.colormap as cm | |
import numpy as np | |
import io | |
from PIL import Image | |
import plotly.graph_objects as go | |
from plotly.subplots import make_subplots | |
import threading | |
# OpenSky API URL | |
BASE_URL = "https://opensky-network.org/api" | |
# Aircraft photos API (예시 - 실제 구현시에는 적절한 API로 대체 필요) | |
AIRCRAFT_PHOTOS_API = "https://api.planespotters.net/pub/photos/hex/{icao24}" | |
def get_aircraft_photo(icao24): | |
"""Get aircraft photo from Planespotters API""" | |
try: | |
response = requests.get(AIRCRAFT_PHOTOS_API.format(icao24=icao24)) | |
data = response.json() | |
if data.get('photos'): | |
return data['photos'][0]['thumbnail_large']['src'] | |
except: | |
# 기본 항공기 이미지 URL 반환 | |
return "https://example.com/default-aircraft.jpg" | |
def get_states(bounds=None): | |
"""Get current aircraft states from OpenSky Network""" | |
params = {} | |
if bounds: | |
params.update({ | |
'lamin': bounds[0], | |
'lomin': bounds[1], | |
'lamax': bounds[2], | |
'lomax': bounds[3] | |
}) | |
try: | |
response = requests.get(f"{BASE_URL}/states/all", params=params) | |
data = response.json() | |
return data | |
except Exception as e: | |
print(f"Error fetching data: {e}") | |
return None | |
def create_monitoring_dashboard(data): | |
"""Create monitoring dashboard using Plotly""" | |
if not data or 'states' not in data: | |
return None | |
states = data['states'] | |
# Create subplots | |
fig = make_subplots( | |
rows=2, cols=2, | |
subplot_titles=('Altitude Distribution', 'Speed Distribution', | |
'Aircraft by Country', 'Aircraft Categories') | |
) | |
# Altitude distribution | |
altitudes = [state[7] for state in states if state[7]] | |
fig.add_trace( | |
go.Histogram(x=altitudes, name="Altitude"), | |
row=1, col=1 | |
) | |
# Speed distribution | |
speeds = [state[9] for state in states if state[9]] | |
fig.add_trace( | |
go.Histogram(x=speeds, name="Speed"), | |
row=1, col=2 | |
) | |
# Aircraft by country | |
countries = pd.Series([state[2] for state in states if state[2]]).value_counts() | |
fig.add_trace( | |
go.Bar(x=countries.index[:10], y=countries.values[:10], name="Countries"), | |
row=2, col=1 | |
) | |
# Aircraft categories | |
categories = pd.Series([state[17] for state in states if state[17]]).value_counts() | |
fig.add_trace( | |
go.Pie(labels=categories.index, values=categories.values, name="Categories"), | |
row=2, col=2 | |
) | |
fig.update_layout( | |
height=800, | |
showlegend=False, | |
template="plotly_dark", | |
paper_bgcolor='rgba(0,0,0,0)', | |
plot_bgcolor='rgba(0,0,0,0)' | |
) | |
return fig | |
def create_map(region="world"): | |
"""Create aircraft tracking map""" | |
bounds = { | |
"world": None, | |
"europe": [35.0, -15.0, 60.0, 40.0], | |
"north_america": [25.0, -130.0, 50.0, -60.0], | |
"asia": [10.0, 60.0, 50.0, 150.0] | |
} | |
data = get_states(bounds.get(region)) | |
if not data or 'states' not in data: | |
return None, None, "Failed to fetch aircraft data" | |
m = folium.Map( | |
location=[30, 0], | |
zoom_start=3, | |
tiles='CartoDB dark_matter' | |
) | |
heat_data = [] | |
# Add aircraft markers | |
for state in data['states']: | |
if state[6] and state[5]: | |
lat, lon = state[6], state[5] | |
callsign = state[1] if state[1] else 'N/A' | |
altitude = state[7] if state[7] else 'N/A' | |
velocity = state[9] if state[9] else 'N/A' | |
icao24 = state[0] | |
heat_data.append([lat, lon, 1]) | |
# Get aircraft photo | |
photo_url = get_aircraft_photo(icao24) | |
popup_content = f""" | |
<div style="font-family: Arial; width: 300px;"> | |
<h4 style="color: #4a90e2;">Flight Information</h4> | |
<img src="{photo_url}" style="width: 100%; max-height: 200px; object-fit: cover; margin-bottom: 10px;"> | |
<p><b>Callsign:</b> {callsign}</p> | |
<p><b>ICAO24:</b> {icao24}</p> | |
<p><b>Altitude:</b> {altitude}m</p> | |
<p><b>Velocity:</b> {velocity}m/s</p> | |
<p><b>Origin:</b> {state[2]}</p> | |
<p><b>Status:</b> {'On Ground' if state[8] else 'In Air'}</p> | |
</div> | |
""" | |
folium.Marker( | |
location=[lat, lon], | |
popup=folium.Popup(popup_content, max_width=300), | |
icon=folium.DivIcon( | |
html=f''' | |
<div style="transform: rotate({state[10]}deg)">✈️</div> | |
''', | |
icon_size=(20, 20) | |
) | |
).add_to(m) | |
plugins.HeatMap(heat_data, radius=15).add_to(m) | |
folium.LayerControl().add_to(m) | |
# Create monitoring dashboard | |
dashboard = create_monitoring_dashboard(data) | |
# Create statistics | |
total_aircraft = len(data['states']) | |
countries = len(set(state[2] for state in data['states'] if state[2])) | |
avg_altitude = np.mean([state[7] for state in data['states'] if state[7]]) if data['states'] else 0 | |
in_air = sum(1 for state in data['states'] if not state[8]) | |
on_ground = sum(1 for state in data['states'] if state[8]) | |
stats = f""" | |
📊 Real-time Statistics: | |
• Total Aircraft: {total_aircraft} | |
• Aircraft in Air: {in_air} | |
• Aircraft on Ground: {on_ground} | |
• Countries: {countries} | |
• Average Altitude: {avg_altitude:.0f}m | |
🔄 Last Updated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} | |
""" | |
return m._repr_html_(), dashboard, stats | |
# Custom CSS | |
custom_css = """ | |
.gradio-container { | |
background: linear-gradient(135deg, #1a1a1a, #2d2d2d) !important; | |
color: #ffffff !important; | |
} | |
.gr-button { | |
background: linear-gradient(135deg, #4a90e2, #357abd) !important; | |
border: none !important; | |
color: white !important; | |
} | |
.gr-button:hover { | |
background: linear-gradient(135deg, #357abd, #4a90e2) !important; | |
transform: translateY(-2px); | |
box-shadow: 0 5px 15px rgba(74, 144, 226, 0.4) !important; | |
} | |
.title-text { | |
text-align: center !important; | |
color: #ffffff !important; | |
font-size: 2.5em !important; | |
margin-bottom: 0.5em !important; | |
text-shadow: 2px 2px 4px rgba(0,0,0,0.5) !important; | |
} | |
.dashboard { | |
background: rgba(0, 0, 0, 0.3) !important; | |
border-radius: 10px !important; | |
padding: 20px !important; | |
} | |
""" | |
with gr.Blocks(css=custom_css) as demo: | |
gr.HTML( | |
""" | |
<div class="title-text">🛩️ Global Aircraft Tracker</div> | |
""" | |
) | |
with gr.Row(): | |
with gr.Column(scale=2): | |
region_select = gr.Dropdown( | |
choices=["world", "europe", "north_america", "asia"], | |
value="world", | |
label="Select Region" | |
) | |
with gr.Column(scale=1): | |
refresh_btn = gr.Button("🔄 Refresh") | |
auto_refresh = gr.Checkbox(label="Auto Refresh", value=False) | |
with gr.Row(): | |
with gr.Column(scale=2): | |
map_html = gr.HTML() | |
with gr.Column(scale=1): | |
stats_text = gr.Textbox(label="Statistics", lines=8) | |
with gr.Row(): | |
dashboard_plot = gr.Plot(label="Monitoring Dashboard") | |
def update_map(region): | |
return create_map(region) | |
def auto_refresh_function(auto_refresh_state): | |
while auto_refresh_state: | |
time.sleep(30) # 30초 대기 | |
map_data, dashboard_data, stats_data = create_map(region_select.value) | |
map_html.update(value=map_data) | |
dashboard_plot.update(value=dashboard_data) | |
stats_text.update(value=stats_data) | |
refresh_btn.click( | |
fn=update_map, | |
inputs=[region_select], | |
outputs=[map_html, dashboard_plot, stats_text] | |
) | |
region_select.change( | |
fn=update_map, | |
inputs=[region_select], | |
outputs=[map_html, dashboard_plot, stats_text] | |
) | |
def handle_auto_refresh(auto_refresh_state): | |
if auto_refresh_state: | |
threading.Thread(target=auto_refresh_function, args=(True,), daemon=True).start() | |
auto_refresh.change( | |
fn=handle_auto_refresh, | |
inputs=[auto_refresh] | |
) | |
# Initial map load | |
map_html, dashboard_plot, stats_text = create_map("world") | |
demo.launch() |