Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
@@ -8,10 +8,27 @@ import time
|
|
8 |
import branca.colormap as cm
|
9 |
import numpy as np
|
10 |
import io
|
|
|
|
|
|
|
11 |
|
12 |
# OpenSky API URL
|
13 |
BASE_URL = "https://opensky-network.org/api"
|
14 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
15 |
def get_states(bounds=None):
|
16 |
"""Get current aircraft states from OpenSky Network"""
|
17 |
params = {}
|
@@ -31,9 +48,60 @@ def get_states(bounds=None):
|
|
31 |
print(f"Error fetching data: {e}")
|
32 |
return None
|
33 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
34 |
def create_map(region="world"):
|
35 |
"""Create aircraft tracking map"""
|
36 |
-
# Default bounds for different regions
|
37 |
bounds = {
|
38 |
"world": None,
|
39 |
"europe": [35.0, -15.0, 60.0, 40.0],
|
@@ -41,51 +109,46 @@ def create_map(region="world"):
|
|
41 |
"asia": [10.0, 60.0, 50.0, 150.0]
|
42 |
}
|
43 |
|
44 |
-
# Get aircraft data
|
45 |
data = get_states(bounds.get(region))
|
46 |
|
47 |
if not data or 'states' not in data:
|
48 |
-
return None, "Failed to fetch aircraft data"
|
49 |
|
50 |
-
# Create base map
|
51 |
m = folium.Map(
|
52 |
location=[30, 0],
|
53 |
zoom_start=3,
|
54 |
tiles='CartoDB dark_matter'
|
55 |
)
|
56 |
|
57 |
-
# Create aircraft icon
|
58 |
-
aircraft_icon = folium.CustomIcon(
|
59 |
-
'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-blue.png',
|
60 |
-
icon_size=(25, 41)
|
61 |
-
)
|
62 |
-
|
63 |
-
# Create heatmap data
|
64 |
heat_data = []
|
65 |
|
66 |
# Add aircraft markers
|
67 |
for state in data['states']:
|
68 |
-
if state[6] and state[5]:
|
69 |
lat, lon = state[6], state[5]
|
70 |
callsign = state[1] if state[1] else 'N/A'
|
71 |
altitude = state[7] if state[7] else 'N/A'
|
72 |
velocity = state[9] if state[9] else 'N/A'
|
|
|
73 |
|
74 |
-
# Add to heatmap data
|
75 |
heat_data.append([lat, lon, 1])
|
76 |
|
77 |
-
#
|
|
|
|
|
78 |
popup_content = f"""
|
79 |
-
<div style="font-family: Arial; width:
|
80 |
<h4 style="color: #4a90e2;">Flight Information</h4>
|
|
|
81 |
<p><b>Callsign:</b> {callsign}</p>
|
|
|
82 |
<p><b>Altitude:</b> {altitude}m</p>
|
83 |
<p><b>Velocity:</b> {velocity}m/s</p>
|
84 |
<p><b>Origin:</b> {state[2]}</p>
|
|
|
85 |
</div>
|
86 |
"""
|
87 |
|
88 |
-
# Add marker
|
89 |
folium.Marker(
|
90 |
location=[lat, lon],
|
91 |
popup=folium.Popup(popup_content, max_width=300),
|
@@ -97,28 +160,31 @@ def create_map(region="world"):
|
|
97 |
)
|
98 |
).add_to(m)
|
99 |
|
100 |
-
# Add heatmap layer
|
101 |
plugins.HeatMap(heat_data, radius=15).add_to(m)
|
102 |
-
|
103 |
-
# Add layer control
|
104 |
folium.LayerControl().add_to(m)
|
105 |
|
106 |
-
#
|
107 |
-
|
108 |
|
109 |
# Create statistics
|
110 |
total_aircraft = len(data['states'])
|
111 |
countries = len(set(state[2] for state in data['states'] if state[2]))
|
112 |
avg_altitude = np.mean([state[7] for state in data['states'] if state[7]]) if data['states'] else 0
|
|
|
|
|
113 |
|
114 |
stats = f"""
|
115 |
-
📊 Statistics:
|
116 |
• Total Aircraft: {total_aircraft}
|
|
|
|
|
117 |
• Countries: {countries}
|
118 |
• Average Altitude: {avg_altitude:.0f}m
|
|
|
|
|
119 |
"""
|
120 |
|
121 |
-
return
|
122 |
|
123 |
# Custom CSS
|
124 |
custom_css = """
|
@@ -143,6 +209,11 @@ custom_css = """
|
|
143 |
margin-bottom: 0.5em !important;
|
144 |
text-shadow: 2px 2px 4px rgba(0,0,0,0.5) !important;
|
145 |
}
|
|
|
|
|
|
|
|
|
|
|
146 |
"""
|
147 |
|
148 |
# Gradio interface
|
@@ -154,15 +225,24 @@ with gr.Blocks(css=custom_css) as demo:
|
|
154 |
)
|
155 |
|
156 |
with gr.Row():
|
157 |
-
|
158 |
-
|
159 |
-
|
160 |
-
|
161 |
-
|
162 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
163 |
|
164 |
-
|
165 |
-
|
166 |
|
167 |
def update_map(region):
|
168 |
return create_map(region)
|
@@ -170,16 +250,25 @@ with gr.Blocks(css=custom_css) as demo:
|
|
170 |
refresh_btn.click(
|
171 |
fn=update_map,
|
172 |
inputs=[region_select],
|
173 |
-
outputs=[map_html, stats_text]
|
174 |
)
|
175 |
|
176 |
region_select.change(
|
177 |
fn=update_map,
|
178 |
inputs=[region_select],
|
179 |
-
outputs=[map_html, stats_text]
|
180 |
)
|
181 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
182 |
# Initial map load
|
183 |
-
map_html, stats_text = create_map("world")
|
184 |
|
185 |
demo.launch()
|
|
|
8 |
import branca.colormap as cm
|
9 |
import numpy as np
|
10 |
import io
|
11 |
+
from PIL import Image
|
12 |
+
import plotly.graph_objects as go
|
13 |
+
from plotly.subplots import make_subplots
|
14 |
|
15 |
# OpenSky API URL
|
16 |
BASE_URL = "https://opensky-network.org/api"
|
17 |
|
18 |
+
# Aircraft photos API (예시 - 실제 구현시에는 적절한 API로 대체 필요)
|
19 |
+
AIRCRAFT_PHOTOS_API = "https://api.planespotters.net/pub/photos/hex/{icao24}"
|
20 |
+
|
21 |
+
def get_aircraft_photo(icao24):
|
22 |
+
"""Get aircraft photo from Planespotters API"""
|
23 |
+
try:
|
24 |
+
response = requests.get(AIRCRAFT_PHOTOS_API.format(icao24=icao24))
|
25 |
+
data = response.json()
|
26 |
+
if data.get('photos'):
|
27 |
+
return data['photos'][0]['thumbnail_large']['src']
|
28 |
+
except:
|
29 |
+
# 기본 항공기 이미지 URL 반환
|
30 |
+
return "https://example.com/default-aircraft.jpg"
|
31 |
+
|
32 |
def get_states(bounds=None):
|
33 |
"""Get current aircraft states from OpenSky Network"""
|
34 |
params = {}
|
|
|
48 |
print(f"Error fetching data: {e}")
|
49 |
return None
|
50 |
|
51 |
+
def create_monitoring_dashboard(data):
|
52 |
+
"""Create monitoring dashboard using Plotly"""
|
53 |
+
if not data or 'states' not in data:
|
54 |
+
return None
|
55 |
+
|
56 |
+
states = data['states']
|
57 |
+
|
58 |
+
# Create subplots
|
59 |
+
fig = make_subplots(
|
60 |
+
rows=2, cols=2,
|
61 |
+
subplot_titles=('Altitude Distribution', 'Speed Distribution',
|
62 |
+
'Aircraft by Country', 'Aircraft Categories')
|
63 |
+
)
|
64 |
+
|
65 |
+
# Altitude distribution
|
66 |
+
altitudes = [state[7] for state in states if state[7]]
|
67 |
+
fig.add_trace(
|
68 |
+
go.Histogram(x=altitudes, name="Altitude"),
|
69 |
+
row=1, col=1
|
70 |
+
)
|
71 |
+
|
72 |
+
# Speed distribution
|
73 |
+
speeds = [state[9] for state in states if state[9]]
|
74 |
+
fig.add_trace(
|
75 |
+
go.Histogram(x=speeds, name="Speed"),
|
76 |
+
row=1, col=2
|
77 |
+
)
|
78 |
+
|
79 |
+
# Aircraft by country
|
80 |
+
countries = pd.Series([state[2] for state in states if state[2]]).value_counts()
|
81 |
+
fig.add_trace(
|
82 |
+
go.Bar(x=countries.index[:10], y=countries.values[:10], name="Countries"),
|
83 |
+
row=2, col=1
|
84 |
+
)
|
85 |
+
|
86 |
+
# Aircraft categories
|
87 |
+
categories = pd.Series([state[17] for state in states if state[17]]).value_counts()
|
88 |
+
fig.add_trace(
|
89 |
+
go.Pie(labels=categories.index, values=categories.values, name="Categories"),
|
90 |
+
row=2, col=2
|
91 |
+
)
|
92 |
+
|
93 |
+
fig.update_layout(
|
94 |
+
height=800,
|
95 |
+
showlegend=False,
|
96 |
+
template="plotly_dark",
|
97 |
+
paper_bgcolor='rgba(0,0,0,0)',
|
98 |
+
plot_bgcolor='rgba(0,0,0,0)'
|
99 |
+
)
|
100 |
+
|
101 |
+
return fig
|
102 |
+
|
103 |
def create_map(region="world"):
|
104 |
"""Create aircraft tracking map"""
|
|
|
105 |
bounds = {
|
106 |
"world": None,
|
107 |
"europe": [35.0, -15.0, 60.0, 40.0],
|
|
|
109 |
"asia": [10.0, 60.0, 50.0, 150.0]
|
110 |
}
|
111 |
|
|
|
112 |
data = get_states(bounds.get(region))
|
113 |
|
114 |
if not data or 'states' not in data:
|
115 |
+
return None, None, "Failed to fetch aircraft data"
|
116 |
|
|
|
117 |
m = folium.Map(
|
118 |
location=[30, 0],
|
119 |
zoom_start=3,
|
120 |
tiles='CartoDB dark_matter'
|
121 |
)
|
122 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
123 |
heat_data = []
|
124 |
|
125 |
# Add aircraft markers
|
126 |
for state in data['states']:
|
127 |
+
if state[6] and state[5]:
|
128 |
lat, lon = state[6], state[5]
|
129 |
callsign = state[1] if state[1] else 'N/A'
|
130 |
altitude = state[7] if state[7] else 'N/A'
|
131 |
velocity = state[9] if state[9] else 'N/A'
|
132 |
+
icao24 = state[0]
|
133 |
|
|
|
134 |
heat_data.append([lat, lon, 1])
|
135 |
|
136 |
+
# Get aircraft photo
|
137 |
+
photo_url = get_aircraft_photo(icao24)
|
138 |
+
|
139 |
popup_content = f"""
|
140 |
+
<div style="font-family: Arial; width: 300px;">
|
141 |
<h4 style="color: #4a90e2;">Flight Information</h4>
|
142 |
+
<img src="{photo_url}" style="width: 100%; max-height: 200px; object-fit: cover; margin-bottom: 10px;">
|
143 |
<p><b>Callsign:</b> {callsign}</p>
|
144 |
+
<p><b>ICAO24:</b> {icao24}</p>
|
145 |
<p><b>Altitude:</b> {altitude}m</p>
|
146 |
<p><b>Velocity:</b> {velocity}m/s</p>
|
147 |
<p><b>Origin:</b> {state[2]}</p>
|
148 |
+
<p><b>Status:</b> {'On Ground' if state[8] else 'In Air'}</p>
|
149 |
</div>
|
150 |
"""
|
151 |
|
|
|
152 |
folium.Marker(
|
153 |
location=[lat, lon],
|
154 |
popup=folium.Popup(popup_content, max_width=300),
|
|
|
160 |
)
|
161 |
).add_to(m)
|
162 |
|
|
|
163 |
plugins.HeatMap(heat_data, radius=15).add_to(m)
|
|
|
|
|
164 |
folium.LayerControl().add_to(m)
|
165 |
|
166 |
+
# Create monitoring dashboard
|
167 |
+
dashboard = create_monitoring_dashboard(data)
|
168 |
|
169 |
# Create statistics
|
170 |
total_aircraft = len(data['states'])
|
171 |
countries = len(set(state[2] for state in data['states'] if state[2]))
|
172 |
avg_altitude = np.mean([state[7] for state in data['states'] if state[7]]) if data['states'] else 0
|
173 |
+
in_air = sum(1 for state in data['states'] if not state[8])
|
174 |
+
on_ground = sum(1 for state in data['states'] if state[8])
|
175 |
|
176 |
stats = f"""
|
177 |
+
📊 Real-time Statistics:
|
178 |
• Total Aircraft: {total_aircraft}
|
179 |
+
• Aircraft in Air: {in_air}
|
180 |
+
• Aircraft on Ground: {on_ground}
|
181 |
• Countries: {countries}
|
182 |
• Average Altitude: {avg_altitude:.0f}m
|
183 |
+
|
184 |
+
🔄 Last Updated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
|
185 |
"""
|
186 |
|
187 |
+
return m._repr_html_(), dashboard, stats
|
188 |
|
189 |
# Custom CSS
|
190 |
custom_css = """
|
|
|
209 |
margin-bottom: 0.5em !important;
|
210 |
text-shadow: 2px 2px 4px rgba(0,0,0,0.5) !important;
|
211 |
}
|
212 |
+
.dashboard {
|
213 |
+
background: rgba(0, 0, 0, 0.3) !important;
|
214 |
+
border-radius: 10px !important;
|
215 |
+
padding: 20px !important;
|
216 |
+
}
|
217 |
"""
|
218 |
|
219 |
# Gradio interface
|
|
|
225 |
)
|
226 |
|
227 |
with gr.Row():
|
228 |
+
with gr.Column(scale=2):
|
229 |
+
region_select = gr.Dropdown(
|
230 |
+
choices=["world", "europe", "north_america", "asia"],
|
231 |
+
value="world",
|
232 |
+
label="Select Region"
|
233 |
+
)
|
234 |
+
with gr.Column(scale=1):
|
235 |
+
refresh_btn = gr.Button("🔄 Refresh")
|
236 |
+
auto_refresh = gr.Checkbox(label="Auto Refresh (30s)", value=False)
|
237 |
+
|
238 |
+
with gr.Row():
|
239 |
+
with gr.Column(scale=2):
|
240 |
+
map_html = gr.HTML()
|
241 |
+
with gr.Column(scale=1):
|
242 |
+
stats_text = gr.Textbox(label="Statistics", lines=8)
|
243 |
|
244 |
+
with gr.Row():
|
245 |
+
dashboard_plot = gr.Plot(label="Monitoring Dashboard")
|
246 |
|
247 |
def update_map(region):
|
248 |
return create_map(region)
|
|
|
250 |
refresh_btn.click(
|
251 |
fn=update_map,
|
252 |
inputs=[region_select],
|
253 |
+
outputs=[map_html, dashboard_plot, stats_text]
|
254 |
)
|
255 |
|
256 |
region_select.change(
|
257 |
fn=update_map,
|
258 |
inputs=[region_select],
|
259 |
+
outputs=[map_html, dashboard_plot, stats_text]
|
260 |
)
|
261 |
|
262 |
+
# Auto refresh
|
263 |
+
if auto_refresh:
|
264 |
+
demo.load(
|
265 |
+
fn=update_map,
|
266 |
+
inputs=[region_select],
|
267 |
+
outputs=[map_html, dashboard_plot, stats_text],
|
268 |
+
every=30
|
269 |
+
)
|
270 |
+
|
271 |
# Initial map load
|
272 |
+
map_html, dashboard_plot, stats_text = create_map("world")
|
273 |
|
274 |
demo.launch()
|