Skip to main content
Glama
xiaonieli7

Flight Ticket MCP Server

by xiaonieli7
simple_opensky_tools.py15.5 kB
""" Simple OpenSky Tools - API航班跟踪工具 提供基础的实时航班查询功能 """ import requests import json import logging from datetime import datetime from typing import Dict, List, Optional, Any import time # 初始化日志器 logger = logging.getLogger(__name__) class SimpleOpenSkyTracker: """航班跟踪器""" def __init__(self): """初始化OpenSky客户端""" self.base_url = "https://opensky-network.org/api" self.session = requests.Session() self.session.headers.update({ 'User-Agent': 'FlightTicketMCP/1.0' }) logger.info("SimpleOpenSky客户端初始化完成") def get_all_states(self, bbox: Optional[tuple] = None) -> Dict[str, Any]: """ 获取所有航班状态 Args: bbox: 可选的边界框 (min_lat, max_lat, min_lon, max_lon) Returns: 包含航班状态的字典 """ try: url = f"{self.base_url}/states/all" params = {} if bbox: params.update({ 'lamin': bbox[0], 'lamax': bbox[1], 'lomin': bbox[2], 'lomax': bbox[3] }) logger.info(f"请求OpenSky API: {url}") response = self.session.get(url, params=params, timeout=30) if response.status_code == 200: data = response.json() return self._parse_states_response(data, bbox) else: logger.warning(f"OpenSky API请求失败: {response.status_code}") return { "status": "error", "message": f"API请求失败: HTTP {response.status_code}" } except requests.exceptions.Timeout: return { "status": "error", "message": "请求超时,OpenSky服务器响应过慢" } except requests.exceptions.RequestException as e: logger.error(f"OpenSky API请求异常: {e}") return { "status": "error", "message": f"网络请求失败: {str(e)}" } except Exception as e: logger.error(f"获取航班状态失败: {e}") return { "status": "error", "message": f"查询失败: {str(e)}" } def _parse_states_response(self, data: Dict, bbox: Optional[tuple] = None) -> Dict[str, Any]: """解析OpenSky API响应数据""" try: if not data or 'states' not in data or data['states'] is None: return { "status": "success", "message": "当前无航班数据", "flights": [], "flight_count": 0, "bbox": bbox, "query_time": datetime.now().isoformat() } flights = [] for state_array in data['states']: if state_array and len(state_array) >= 17: # 确保有足够的数据 flight_info = self._parse_state_vector(state_array) if flight_info: flights.append(flight_info) return { "status": "success", "message": f"成功获取 {len(flights)} 架航班信息", "flights": flights, "flight_count": len(flights), "bbox": bbox, "query_time": datetime.now().isoformat(), "data_source": "opensky_network_rest" } except Exception as e: logger.error(f"解析航班数据失败: {e}") return { "status": "error", "message": f"数据解析失败: {str(e)}" } def _parse_state_vector(self, state_array: List) -> Optional[Dict[str, Any]]: """ 解析单个状态向量 OpenSky API状态向量格式: [0] icao24, [1] callsign, [2] origin_country, [3] time_position, [4] last_contact, [5] longitude, [6] latitude, [7] baro_altitude, [8] on_ground, [9] velocity, [10] true_track, [11] vertical_rate, [12] sensors, [13] geo_altitude, [14] squawk, [15] spi, [16] position_source """ try: if len(state_array) < 17: return None icao24 = state_array[0] callsign = state_array[1].strip() if state_array[1] else None origin_country = state_array[2] longitude = state_array[5] latitude = state_array[6] baro_altitude = state_array[7] # 气压高度 on_ground = state_array[8] velocity = state_array[9] # 地面速度 m/s true_track = state_array[10] # 真航迹角 vertical_rate = state_array[11] # 垂直速度 m/s geo_altitude = state_array[13] # 几何高度 last_contact = state_array[4] # 计算航班状态 status = "UNKNOWN" if on_ground: status = "ON_GROUND" elif velocity and velocity > 100: # 100 m/s ≈ 360 km/h status = "AIRBORNE" elif velocity and velocity > 10: status = "TAXI" else: status = "STATIONARY" # 选择更可靠的高度数据 altitude = baro_altitude if baro_altitude else geo_altitude return { "icao24": icao24, "callsign": callsign, "origin_country": origin_country, "position": { "longitude": longitude, "latitude": latitude, "altitude_meters": altitude, "altitude_feet": round(altitude * 3.28084) if altitude else None }, "velocity": { "ground_speed_ms": velocity, "ground_speed_kmh": round(velocity * 3.6) if velocity else None, "vertical_rate_ms": vertical_rate }, "true_track": true_track, "on_ground": on_ground, "status": status, "last_contact": datetime.fromtimestamp(last_contact).isoformat() if last_contact else None, "last_contact_seconds_ago": int(time.time() - last_contact) if last_contact else None } except Exception as e: logger.warning(f"解析状态向量失败: {e}") return None def search_flights_by_callsign(self, callsign_pattern: str) -> Dict[str, Any]: """根据呼号模式搜索航班""" all_states = self.get_all_states() if all_states.get("status") != "success": return all_states matching_flights = [] for flight in all_states.get("flights", []): if flight.get("callsign"): if callsign_pattern.upper() in flight["callsign"].upper(): matching_flights.append(flight) return { "status": "success", "message": f"找到 {len(matching_flights)} 架匹配航班", "search_pattern": callsign_pattern, "flights": matching_flights, "flight_count": len(matching_flights), "query_time": datetime.now().isoformat(), "data_source": "opensky_network_rest" } def get_airport_area_flights(self, airport_code: str) -> Dict[str, Any]: """获取机场区域的航班""" # 中国主要机场坐标(数据来源:中国开放数据平台等) airport_coords = { "PEK": (40.0801, 116.5844), # 北京首都国际机场 "PKX": (39.5098, 116.4107), # 北京大兴国际机场 "PVG": (31.1434, 121.8052), # 上海浦东国际机场 "SHA": (31.1979, 121.3364), # 上海虹桥国际机场 "CAN": (23.3925, 113.2989), # 广州白云国际机场 "SZX": (22.6393, 113.8107), # 深圳宝安国际机场 "CKG": (29.7194, 106.6419), # 重庆江北国际机场 "TSN": (39.1244, 117.3469), # 天津滨海国际机场 "CTU": (30.5786, 103.9472), # 成都双流国际机场 "TFU": (30.3114, 104.4419), # 成都天府国际机场 "KMG": (25.1019, 102.9292), # 昆明长水国际机场 "XIY": (34.4471, 108.7519), # 西安咸阳国际机场 "HGH": (30.2295, 120.4344), # 杭州萧山国际机场 "NKG": (31.7420, 118.8620), # 南京禄口国际机场 "WUH": (30.7838, 114.2081), # 武汉天河国际机场 "CSX": (28.1892, 113.2196), # 长沙黄花国际机场 "TAO": (36.2661, 120.3744), # 青岛流亭国际机场 "XMN": (24.5440, 118.1277), # 厦门高崎国际机场 "FOC": (25.9351, 119.6633), # 福州长乐国际机场 "NNG": (22.6083, 108.1722), # 南宁吴圩国际机场 "KWE": (26.5385, 106.8007), # 贵阳龙洞堡国际机场 "SJW": (38.2806, 114.6963), # 石家庄正定国际机场 "TYN": (37.7469, 112.6286), # 太原武宿国际机场 "HET": (40.8514, 111.8244), # 呼和浩特白塔国际机场 "SHE": (41.6398, 123.4836), # 沈阳桃仙国际机场 "CGQ": (43.9961, 125.6850), # 长春龙嘉国际机场 "HRB": (45.6234, 126.2507), # 哈尔滨太平国际机场 "NKQ": (25.6675, 100.2769), # 南昌昌北国际机场(新增) "LHW": (34.7414, 113.8406), # 兰州中川国际机场 "INC": (38.8531, 106.0094), # 银川河东国际机场 "XNN": (36.5275, 102.0430), # 西宁曹家堡机场 "URC": (43.9071, 87.4744), # 乌鲁木齐地窝堡国际机场 "SYX": (18.3027, 109.4122), # 三亚凤凰国际机场 "HAK": (19.9349, 110.4590), # 海口美兰国际机场 "DLC": (38.9656, 121.5386), # 大连周水子国际机场 "YNT": (37.4017, 121.3717), # 烟台蓬莱国际机场 "WEH": (37.1871, 122.2286), # 威海大水泊国际机场 "JZH": (35.0286, 118.6414), # 济南遥墙国际机场 "LYG": (34.5714, 119.1286), # 连云港白塔埠机场 "YTY": (32.5631, 119.7197), # 扬州泰州国际机场 "WUX": (31.4944, 120.4292), # 无锡硕放国际机场 "NTG": (32.0708, 120.9764), # 南通兴东国际机场 "HFE": (31.7800, 117.2981), # 合肥新桥国际机场 "WNZ": (27.9122, 120.8522), # 温州龙湾国际机场 "NGB": (29.8267, 121.4619), # 宁波栎社国际机场 "YIW": (29.3447, 120.0322), # 义乌机场 "BHY": (49.2050, 119.8250), # 北海福成机场 "LZH": (24.2075, 109.3917), # 柳州白莲机场 "GXG": (24.7953, 110.0381), # 桂林两江国际机场 "ZUH": (22.0064, 113.3758), # 珠海金湾机场 "MXZ": (24.2783, 116.1222), # 梅县机场 "SWA": (23.5619, 116.5086), # 汕头外砂机场 "JYG": (24.1436, 116.6664), # 揭阳潮汕机场 "ZHA": (21.2144, 110.3583), # 湛江机场 "BAV": (23.7208, 106.9592), # 百色巴马机场 } if airport_code.upper() not in airport_coords: return { "status": "error", "message": f"不支持的机场代码: {airport_code}", "supported_airports": list(airport_coords.keys()) } lat, lon = airport_coords[airport_code.upper()] # 创建机场周边边界框(约30公里范围) delta = 0.25 # 约30公里 bbox = (lat - delta, lat + delta, lon - delta, lon + delta) result = self.get_all_states(bbox) if result.get("status") == "success": result["airport_code"] = airport_code.upper() result["airport_coordinates"] = {"latitude": lat, "longitude": lon} result["search_radius_km"] = 30 result["message"] = f"{airport_code}机场周边找到 {result.get('flight_count', 0)} 架航班" return result # 全局实例 simple_tracker = SimpleOpenSkyTracker() def getFlightStatus(flight_number: str, date: str = None) -> Dict[str, Any]: """ 查询航班实时状态(使用OpenSky Network REST API) Args: flight_number: 航班号/呼号 (如: "CCA1234", "CSN5678") date: 日期参数(OpenSky仅支持实时数据,此参数被忽略) Returns: 包含航班状态信息的字典 """ if date: logger.info("OpenSky仅支持实时数据,忽略date参数") return simple_tracker.search_flights_by_callsign(flight_number) def getAirportFlights(airport_code: str, flight_type: str = "departure") -> Dict[str, Any]: """ 查询机场周边的航班信息 Args: airport_code: 机场代码 (如: "PEK", "PVG", "CAN") flight_type: 航班类型(此参数仅为兼容性,OpenSky返回所有航班) Returns: 包含机场周边航班列表的字典 """ return simple_tracker.get_airport_area_flights(airport_code) def getFlightsInArea(min_lat: float, max_lat: float, min_lon: float, max_lon: float) -> Dict[str, Any]: """ 查询指定地理区域内的所有航班 Args: min_lat: 最小纬度 max_lat: 最大纬度 min_lon: 最小经度 max_lon: 最大经度 Returns: 包含区域内航班列表的字典 """ bbox = (min_lat, max_lat, min_lon, max_lon) return simple_tracker.get_all_states(bbox) def trackMultipleFlights(flight_numbers: List[str], date: str = None) -> Dict[str, Any]: """ 批量跟踪多个航班状态 Args: flight_numbers: 航班号列表 date: 日期参数(OpenSky仅支持实时数据) Returns: 包含所有航班状态的字典 """ if date: logger.info("OpenSky仅支持实时数据,忽略date参数") logger.info(f"批量查询航班状态: {flight_numbers}") results = [] for flight_number in flight_numbers: result = getFlightStatus(flight_number) results.append(result) time.sleep(1) # 避免API频率限制 successful_count = sum(1 for r in results if r.get("status") == "success" and r.get("flight_count", 0) > 0) return { "status": "success", "message": f"批量查询完成,共查询{len(flight_numbers)}个航班,找到{successful_count}个", "flight_count": len(flight_numbers), "found_count": successful_count, "results": results, "query_time": datetime.now().isoformat(), "data_source": "opensky_network_rest", "note": "OpenSky仅提供实时数据,无法查询历史航班信息" }

Implementation Reference

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/xiaonieli7/FlightTicketMCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server