Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import folium | |
| from folium import plugins | |
| import requests | |
| from requests.auth import HTTPBasicAuth | |
| import pandas as pd | |
| from datetime import datetime | |
| import time | |
| import numpy as np | |
| import plotly.graph_objects as go | |
| from plotly.subplots import make_subplots | |
| import os | |
| # OpenSky API 인증 정보를 환경 변수에서 가져오기 | |
| USERNAME = os.getenv("ID_AUTH") | |
| PASSWORD = os.getenv("PW_AUTH") | |
| class OpenSkyApi: | |
| def __init__(self, username=None, password=None): | |
| self.auth = HTTPBasicAuth(username, password) if username and password else None | |
| self.base_url = "https://opensky-network.org/api" | |
| def get_states(self, time_secs=0, icao24=None, bbox=None): | |
| """Retrieve state vectors for a given time.""" | |
| params = {"time": int(time_secs) if time_secs else int(time.time())} | |
| if icao24: | |
| params["icao24"] = icao24 | |
| if bbox: | |
| params.update({ | |
| "lamin": bbox[0], | |
| "lamax": bbox[1], | |
| "lomin": bbox[2], | |
| "lomax": bbox[3] | |
| }) | |
| headers = { | |
| 'User-Agent': 'Mozilla/5.0', | |
| 'Accept': 'application/json' | |
| } | |
| try: | |
| response = requests.get( | |
| f"{self.base_url}/states/all", | |
| params=params, | |
| auth=self.auth, | |
| headers=headers, | |
| timeout=15 | |
| ) | |
| if response.status_code == 200: | |
| return StateVector(response.json()) | |
| elif response.status_code == 401: | |
| print("Authentication failed. Please check your environment variables ID_AUTH and PW_AUTH.") | |
| return None | |
| else: | |
| print(f"Error {response.status_code}: {response.text}") | |
| return None | |
| except Exception as e: | |
| print(f"Error fetching data: {e}") | |
| return None | |
| class StateVector: | |
| def __init__(self, states_json): | |
| self.time = states_json.get('time', 0) | |
| self.states = [] | |
| if states_json.get('states'): | |
| for state in states_json['states']: | |
| self.states.append(State(state)) | |
| class State: | |
| def __init__(self, state_array): | |
| self.icao24 = state_array[0] | |
| self.callsign = state_array[1] | |
| self.origin_country = state_array[2] | |
| self.time_position = state_array[3] | |
| self.last_contact = state_array[4] | |
| self.longitude = state_array[5] | |
| self.latitude = state_array[6] | |
| self.geo_altitude = state_array[7] | |
| self.on_ground = state_array[8] | |
| self.velocity = state_array[9] | |
| self.true_track = state_array[10] | |
| self.vertical_rate = state_array[11] | |
| self.sensors = state_array[12] if len(state_array) > 12 else None | |
| self.baro_altitude = state_array[13] if len(state_array) > 13 else None | |
| self.squawk = state_array[14] if len(state_array) > 14 else None | |
| self.spi = state_array[15] if len(state_array) > 15 else None | |
| self.position_source = state_array[16] if len(state_array) > 16 else None | |
| # OpenSky API 인스턴스 생성 | |
| api = OpenSkyApi(USERNAME, PASSWORD) | |
| def get_states(bounds=None, max_retries=3): | |
| """Get current aircraft states from OpenSky Network with retry logic""" | |
| for attempt in range(max_retries): | |
| try: | |
| if bounds: | |
| bbox = ( | |
| bounds['lamin'], | |
| bounds['lamax'], | |
| bounds['lomin'], | |
| bounds['lomax'] | |
| ) | |
| states = api.get_states(bbox=bbox) | |
| else: | |
| states = api.get_states() | |
| if states and states.states: | |
| return {'states': states.states} | |
| if attempt < max_retries - 1: | |
| wait_time = min(2 ** attempt, 60) | |
| print(f"Retrying in {wait_time} seconds...") | |
| time.sleep(wait_time) | |
| continue | |
| return None | |
| except Exception as e: | |
| print(f"Error fetching data: {e}") | |
| if attempt < max_retries - 1: | |
| time.sleep(5) | |
| continue | |
| return None | |
| return None | |
| def create_monitoring_dashboard(states): | |
| """Create monitoring dashboard using Plotly""" | |
| if not states: | |
| return go.Figure() | |
| # StateVector 객체에서 데이터 추출 | |
| altitudes = [s.geo_altitude for s in states if s.geo_altitude is not None] | |
| speeds = [s.velocity for s in states if s.velocity is not None] | |
| countries = pd.Series([s.origin_country for s in states if s.origin_country]) | |
| # Create subplots | |
| fig = make_subplots( | |
| rows=2, cols=2, | |
| subplot_titles=('Altitude Distribution', 'Speed Distribution', | |
| 'Aircraft by Country', 'Aircraft Categories'), | |
| specs=[ | |
| [{"type": "xy"}, {"type": "xy"}], | |
| [{"type": "xy"}, {"type": "domain"}] | |
| ] | |
| ) | |
| # Add traces | |
| fig.add_trace( | |
| go.Histogram(x=altitudes, name="Altitude", marker_color='#4a90e2'), | |
| row=1, col=1 | |
| ) | |
| fig.add_trace( | |
| go.Histogram(x=speeds, name="Speed", marker_color='#50C878'), | |
| row=1, col=2 | |
| ) | |
| # Top 10 countries | |
| top_countries = countries.value_counts().head(10) | |
| fig.add_trace( | |
| go.Bar( | |
| x=top_countries.index, | |
| y=top_countries.values, | |
| name="Countries", | |
| marker_color='#FF6B6B' | |
| ), | |
| row=2, col=1 | |
| ) | |
| # Aircraft categories | |
| categories = ['Commercial', 'Private', 'Military', 'Other'] | |
| values = [40, 30, 20, 10] # Example distribution | |
| fig.add_trace( | |
| go.Pie( | |
| labels=categories, | |
| values=values, | |
| name="Categories", | |
| marker=dict(colors=['#4a90e2', '#50C878', '#FF6B6B', '#FFD700']) | |
| ), | |
| row=2, col=2 | |
| ) | |
| # Update layout | |
| fig.update_layout( | |
| height=800, | |
| showlegend=True, | |
| template="plotly_dark", | |
| paper_bgcolor='rgba(0,0,0,0.3)', | |
| plot_bgcolor='rgba(0,0,0,0.1)', | |
| margin=dict(l=50, r=50, t=50, b=50), | |
| font=dict(color='white'), | |
| legend=dict( | |
| bgcolor='rgba(0,0,0,0.3)', | |
| bordercolor='rgba(255,255,255,0.2)', | |
| borderwidth=1 | |
| ) | |
| ) | |
| fig.update_xaxes(gridcolor='rgba(255,255,255,0.1)', zeroline=False) | |
| fig.update_yaxes(gridcolor='rgba(255,255,255,0.1)', zeroline=False) | |
| for i in fig['layout']['annotations']: | |
| i['font'] = dict(size=12, color='white') | |
| return fig | |
| def create_map(region="world"): | |
| """Create aircraft tracking map""" | |
| m = folium.Map( | |
| location=[30, 0], | |
| zoom_start=3, | |
| tiles='CartoDB dark_matter' | |
| ) | |
| bounds = { | |
| "world": None, | |
| "europe": {"lamin": 35.0, "lomin": -15.0, "lamax": 60.0, "lomax": 40.0}, | |
| "north_america": {"lamin": 25.0, "lomin": -130.0, "lamax": 50.0, "lomax": -60.0}, | |
| "asia": {"lamin": 10.0, "lomin": 60.0, "lamax": 50.0, "lomax": 150.0} | |
| } | |
| data = get_states(bounds.get(region)) | |
| if not data or not data['states']: | |
| return ( | |
| m._repr_html_(), | |
| create_monitoring_dashboard([]), | |
| "No data available. Please try again later." | |
| ) | |
| states = data['states'] | |
| heat_data = [] | |
| for state in states: | |
| if state.latitude and state.longitude: | |
| lat, lon = state.latitude, state.longitude | |
| callsign = state.callsign if state.callsign else 'N/A' | |
| altitude = state.geo_altitude if state.geo_altitude else 'N/A' | |
| velocity = state.velocity if state.velocity else 'N/A' | |
| heat_data.append([lat, lon, 1]) | |
| popup_content = f""" | |
| <div style="font-family: Arial; width: 200px;"> | |
| <h4 style="color: #4a90e2;">Flight Information</h4> | |
| <p><b>Callsign:</b> {callsign}</p> | |
| <p><b>Altitude:</b> {altitude}m</p> | |
| <p><b>Velocity:</b> {velocity}m/s</p> | |
| <p><b>Origin:</b> {state.origin_country}</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.true_track if state.true_track else 0}deg)">✈️</div> | |
| ''', | |
| icon_size=(20, 20) | |
| ) | |
| ).add_to(m) | |
| plugins.HeatMap(heat_data, radius=15).add_to(m) | |
| total_aircraft = len(states) | |
| countries = len(set(s.origin_country for s in states if s.origin_country)) | |
| avg_altitude = np.mean([s.geo_altitude for s in states if s.geo_altitude is not None]) if states else 0 | |
| stats = f""" | |
| 📊 Real-time Statistics: | |
| • Total Aircraft: {total_aircraft} | |
| • Countries: {countries} | |
| • Average Altitude: {avg_altitude:.0f}m | |
| 🔄 Last Updated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} | |
| """ | |
| return m._repr_html_(), create_monitoring_dashboard(states), stats | |
| # Custom CSS | |
| custom_css = """ | |
| .gradio-container { | |
| background: linear-gradient(135deg, #1a1a1a, #2d2d2d) !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; | |
| } | |
| """ | |
| # Gradio interface | |
| with gr.Blocks(css=custom_css) as demo: | |
| gr.HTML( | |
| """ | |
| <h1 style="text-align: center; color: white;">🛩️ Global Aircraft Tracker</h1> | |
| <p style="text-align: center; color: #ccc;">Real-time tracking of aircraft worldwide</p> | |
| """ | |
| ) | |
| gr.HTML("""<a href="https://visitorbadge.io/status?path=https%3A%2F%2Fimmunobiotech-opensky.hf.space"> | |
| <img src="https://api.visitorbadge.io/api/visitors?path=https%3A%2F%2Fimmunobiotech-opensky.hf.space&countColor=%23263759" /> | |
| </a>""") | |
| with gr.Row(): | |
| region_select = gr.Dropdown( | |
| choices=["world", "europe", "north_america", "asia"], | |
| value="world", | |
| label="Select Region" | |
| ) | |
| refresh_btn = gr.Button("🔄 Refresh") | |
| map_html = gr.HTML() | |
| stats_text = gr.Textbox(label="Statistics", lines=6) | |
| dashboard_plot = gr.Plot(label="Monitoring Dashboard") | |
| def update_map(region): | |
| try: | |
| return create_map(region) | |
| except Exception as e: | |
| print(f"Error updating map: {e}") | |
| return ( | |
| "<p>Map loading failed. Please try again.</p>", | |
| go.Figure(), | |
| f"Error: {str(e)}" | |
| ) | |
| 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] | |
| ) | |
| # 환경 변수 확인 | |
| if not USERNAME or not PASSWORD: | |
| print("Warning: Environment variables ID_AUTH and/or PW_AUTH are not set.") | |
| print("The application will run with anonymous access, which has lower rate limits.") | |
| # Launch with specific configurations | |
| demo.launch( | |
| show_error=True, | |
| server_name="0.0.0.0", | |
| server_port=7860, | |
| share=False | |
| ) |