news 2026/9/5 9:57:07

【Bonjour】MATLAB + OpenSky API:基于公开数据的实时飞机状态监控系统

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【Bonjour】MATLAB + OpenSky API:基于公开数据的实时飞机状态监控系统

🛩️ MATLAB + OpenSky API:基于公开数据的实时飞机状态监控系统

你有没有想过,像 Flightradar24 这样的实时航班追踪网站是如何工作的?其实,这背后依赖的是一种叫做ADS‑B(广播式自动相关监视)的公开广播技术。

每架现代飞机都会持续向外广播自己的身份、位置、速度、高度等信息,任何人都可以通过 ADS‑B 接收器捕获这些公开数据。而像OpenSky NetworkADS‑B Exchange这样的平台,则在全球范围内收集这些广播数据,并向开发者提供免费的 API 接口。

今天,我们就用MATLAB来写一个小程序,通过 OpenSky 的免费 API 获取任意一架飞机的实时状态数据,并实现以下几个功能:

  • 📡 实时查询飞机的经纬度、高度、速度、航向
  • 🛫 自动判断起飞 / 降落事件
  • 🗺️ 根据坐标匹配最近的机场
  • 🌐 自动打开 ADS‑B Exchange 地图进行可视化

本文纯属技术研究与学习交流,所有数据均来源于公开接口,请勿用于任何侵犯他人隐私或违反法律法规的用途。


马的飞机

孙的飞机(之前许的A330)

🎯 最终效果:交互菜单 + 实时日志 + 自动地图

运行main.m后,你会看到一个友好的交互菜单:

===== 飞机实时追踪器 ===== 当前追踪列表: 1. 示例飞机A (ICAO: A835AF) 2. 示例飞机B (ICAO: 424504) 3. 示例飞机C (ICAO: A15C43) 选项: [数字] 选择目标 [A] 添加新目标 [D] 删除目标 [M] 修改 ICAO [Q] 退出

选择一个目标后,程序会每 30 秒轮询一次 OpenSky API,并在控制台输出实时状态:

[04-Sep-2026 07:09:22] 开始检查... 飞机 (ICAO: A15C43) 坐标无效(lat=[], lon=[]),无法获取机场位置。

如果飞机在线且位置有效,还会显示停靠机场信息:

🛬 飞机停靠在 Van Nuys Airport (Los Angeles, US),距离 0.53 英里

当检测到起飞或降落时,会输出醒目的事件消息:

✈️ 起飞!从 Van Nuys Airport (Los Angeles, US) 🛬 降落!在 Los Angeles International Airport (Los Angeles, US) 飞行时间 1小时23分

同时,浏览器会自动打开ADS‑B Exchange的地图页面,直接定位到该飞机,方便进行可视化观察。


🧠 核心技术原理

🛩️ 飞机的“数字身份证”:ICAO 24位地址

在代码中,我们输入的A835AF424504这类看似随机的字符串,其实是飞机的ICAO 24 位地址码

每一架装备了 Mode S 应答机的飞机,都会被国际民航组织(ICAO)分配一个全球唯一的 24 位二进制地址,通常表示为6 位的十六进制数(例如A835AF)。

你可以把它理解为飞机的“数字身份证号”——飞机在飞行时会通过 ADS‑B 技术持续广播这个地址码,连同其经纬度、高度、速度、航向等信息。这是一个公开的广播,任何人都可以接收。

因此,我们代码中的icao24变量,本质上就是利用这个“身份证号”去 OpenSky 的数据库里查询该飞机的实时状态。


💻 代码实现详解

1. 数据获取:调用 OpenSky API

getOpenSkyData.m函数负责向 OpenSky Network 发送 HTTP 请求,获取指定 ICAO 地址的实时状态:

functionstate=getOpenSkyData(icao24,opts)baseURL='https://opensky-network.org/api/states/all';params={'icao24',lower(icao24)};data=webread(baseURL,params{:});fori=1:size(data.states,1)ifstrcmpi(data.states{i,1},icao24)state.icao24=data.states{i,1};state.callsign=data.states{i,2};state.longitude=data.states{i,5};state.latitude=data.states{i,6};state.baro_altitude=data.states{i,7};% 单位:米state.on_ground=data.states{i,8};state.velocity=data.states{i,9};% 单位:m/sstate.true_track=data.states{i,10};returnendendstate=[];end

注意:受 FAA 隐私保护政策(PIA)影响,部分美国注册的飞机在 OpenSky 中可能查不到位置数据,此时函数会返回空数组,程序会自动处理这种情况。


2. 状态管理:飞机对象封装

Plane.m定义了一个Plane类,用于封装飞机的所有状态信息及操作逻辑:

classdef Plane<handle properties icao% ICAO 地址callsign% 呼号latitude% 纬度longitude% 经度alt_ft% 高度(英尺)on_ground% 是否在地面speed% 速度(m/s)track% 航向last_on_ground% 上一轮地面状态last_alt_ft% 上一轮高度tookoff% 起飞事件标记landed% 降落事件标记nearestAirport% 最近机场信息end% ... 方法定义end

面向对象的设计让代码更加清晰,状态管理和事件检测逻辑都内聚在类中。


3. 事件检测:起飞 / 降落判定

runCheck方法是整个程序的核心逻辑。它通过对比当前状态与历史状态来判断飞机是否发生了起飞或降落:

% 起飞检测ifobj.below_desired&&~obj.on_groundifobj.last_on_ground==true||(obj.last_feeding==false&&obj.last_alt_ft<DESIRED_FT)obj.tookoff=true;obj.takeoff_time=datetime('now');obj.nearestAirport=getNearestAirport(obj.latitude,obj.longitude,airports);endend% 降落检测ifobj.on_ground&&~obj.last_on_ground&&obj.last_below_desired obj.landed=true;obj.nearestAirport=getNearestAirport(obj.latitude,obj.longitude,airports);end

判定逻辑

  • 起飞:当前低于 15000 英尺且在空中,而上一轮在地面(或上一轮无数据但高度也很低)
  • 降落:当前在地面,而上一轮在空中且高度低于阈值

4. 位置匹配:Haversine 公式求最近机场

当飞机停在地面或发生起降事件时,getNearestAirport函数通过Haversine 公式计算飞机位置与所有机场之间的距离,返回最近的机场信息:

R=3959;% 地球半径(英里)dlat=deg2rad(latVec-lat);dlon=deg2rad(lonVec-lon);a=sin(dlat/2).^2+cos(deg2rad(lat)).*cos(deg2rad(latVec)).*sin(dlon/2).^2;c=2*atan2(sqrt(a),sqrt(1-a));dist=R*c;[minDist,idxMin]=min(dist);airportRow=filtered(idxMin,:);

该函数会从airports.csv(OurAirports 开源数据库)中筛选出民用机场,然后计算距离最近的一个。


🌐 可视化辅助:自动打开 ADS‑B Exchange 地图

由于 OpenSky 的 API 可能会因为隐私政策等原因无法返回某些飞机的位置,程序集成了ADS‑B Exchange的地图功能作为补充:

web(sprintf('https://globe.adsbexchange.com/?icao=%s',lower(icao24)),'-browser');

ADS‑B Exchange 以其“不过滤数据”著称,大多数在 OpenSky 中被屏蔽的飞机,在这里都能看到。命令行日志与地图可视化双管齐下,监控更全面。


🛠️ 快速上手

环境准备

  • MATLAB R2019b 或更高版本(需要支持webreaddatetime类型)
  • 网络连接(首次运行需下载机场数据库)

运行步骤

  1. 下载所有.m文件到同一文件夹
  2. 运行main.m,首次运行会自动下载airports.csv(约 5 MB)
  3. A添加目标飞机,输入自定义名称和 ICAO 地址(6 位十六进制)
    • ICAO 地址可在 ADS‑B Exchange 等网站上查询
  4. 输入编号开始追踪
  5. 可选配置 Discord 通知:修改config.DISCORD.ENABLE'true',并填写 Webhook URL

🚁 应用场景

这个工具不仅仅是技术演示,它在多个实际场景中都有实用价值:

  • 🚨应急救援:监控救灾直升机是否按计划起飞、降落
  • 📦物流追踪:跟踪医疗物资运输机的实时位置
  • 🔬航空研究:采集飞行轨迹数据用于分析
  • 🎓教学演示:展示 MATLAB 网络编程、API 调用、实时数据处理

📦 完整代码

1.main.m- 主程序

clc;clear;close all;% 自动打开 ADS-B Exchange 首页web('https://globe.adsbexchange.com/','-browser');%% 初始化ensureDataFiles();%% 配置config.DATA.INTERVAL='30';config.DATA.DATA_LOSS_MINS='5';config.OPENSKY.USERNAME='';config.OPENSKY.PASSWORD='';config.DISCORD.ENABLE='false';config.DISCORD.WEBHOOK_URL='https://...';config.DISCORD.ROLE_ID='';%% 目标列表(仅作技术示例,请使用合法公开的 ICAO)aircraftList={'示例飞机A','A835AF';'示例飞机B','424504';'示例飞机C','A15C43';};%% 交互菜单fprintf('\n===== 飞机实时追踪器 =====\n');whiletruefprintf('\n当前追踪列表:\n');fori=1:size(aircraftList,1)fprintf(' %d. %s (ICAO: %s)\n',i,aircraftList{i,1},aircraftList{i,2});endfprintf('\n选项:\n');fprintf(' [数字] 选择目标\n');fprintf(' [A] 添加新目标\n');fprintf(' [D] 删除目标\n');fprintf(' [M] 修改 ICAO\n');fprintf(' [Q] 退出\n');choice=upper(input('请输入选项:','s'));switchchoicecase'Q',fprintf('再见!\n');return;case'A'name=input('名称:','s');icao=input('ICAO 地址:','s');aircraftList(end+1,:)={name,upper(icao)};case'D'idx=input('编号:');ifidx>=1&&idx<=size(aircraftList,1)aircraftList(idx,:)=[];endcase'M'idx=input('编号:');ifidx>=1&&idx<=size(aircraftList,1)newIcao=input('新 ICAO:','s');aircraftList{idx,2}=upper(newIcao);endotherwiseidx=str2double(choice);ifisnan(idx)||idx<1||idx>size(aircraftList,1)fprintf('无效选项\n');continue;endicao24=aircraftList{idx,2};owner=aircraftList{idx,1};fprintf('\n开始追踪 %s (ICAO: %s)\n',owner,icao24);web(sprintf('https://globe.adsbexchange.com/?icao=%s',lower(icao24)),'-browser');break;endend%% 加载机场库 & 启动追踪airports=readAirports('airports.csv');plane=Plane(icao24,str2double(config.DATA.DATA_LOSS_MINS));interval=str2double(config.DATA.INTERVAL);fprintf('追踪已启动,每 %d 秒检查一次。按 Ctrl+C 停止。\n',interval);whiletruetrystate=getOpenSkyData(icao24,config.OPENSKY);ifisempty(state)plane=plane.runEmpty();elseplane=plane.updateState(state);endplane=plane.runCheck(airports);ifplane.on_ground&&~plane.hasEvent()lat=plane.latitude;lon=plane.longitude;if~isempty(lat)&&~isempty(lon)&&isscalar(lat)&&isscalar(lon)&&isfinite(lat)&&isfinite(lon)trynearest=getNearestAirport(lat,lon,airports);if~strcmp(nearest.icao,plane.lastGroundIcao)fprintf('🛬 飞机停靠在 %s (%s, %s),距离 %.2f 英里\n',...nearest.name,nearest.municipality,nearest.iso_country,nearest.distance_mi);plane.lastGroundIcao=nearest.icao;endcatchendelsefprintf('⚠️ 坐标无效,无法获取机场位置(可能被隐私屏蔽)\n');endelseplane.lastGroundIcao='';endifplane.hasEvent()msg=plane.generateMessage();fprintf('🔔 %s\n',msg);ifstrcmpi(config.DISCORD.ENABLE,'true')sendDiscordNotification(msg,config.DISCORD.WEBHOOK_URL,config.DISCORD.ROLE_ID);endplane.clearEvent();endpause(interval);catchMEfprintf('❌ 错误:%s\n',ME.message);pause(interval);endend

2.Plane.m- 飞机类

classdef Plane<handle properties icao,callsign,latitude,longitude,alt_ft,on_ground,speed,track last_pos_time,last_on_ground,last_alt_ft,last_feeding below_desired,last_below_desired tookoff,landed,takeoff_time,dataLossMins,nearestAirport,lastGroundIcaoendmethodsfunctionobj=Plane(icao,dataLossMins)obj.icao=icao;obj.dataLossMins=dataLossMins;obj.on_ground=true;obj.last_on_ground=true;obj.last_feeding=false;obj.below_desired=false;obj.last_below_desired=false;obj.last_alt_ft=0;obj.tookoff=false;obj.landed=false;obj.takeoff_time=[];obj.lastGroundIcao='';endfunctionobj=updateState(obj,state)obj.callsign=state.callsign;obj.latitude=state.latitude;obj.longitude=state.longitude;obj.on_ground=safeLogical(state.on_ground);if~isempty(state.baro_altitude)&&isscalar(state.baro_altitude)obj.alt_ft=double(state.baro_altitude)*3.28084;elseobj.alt_ft=0;endobj.speed=state.velocity;obj.track=state.true_track;obj.last_pos_time=datetime('now');obj.last_feeding=true;endfunctionobj=runEmpty(obj)obj.last_feeding=false;obj=obj.runCheck([]);endfunctionobj=runCheck(obj,airports)DESIRED_FT=15000;obj.alt_ft=ensureScalarDouble(obj.alt_ft,0);obj.on_ground=safeLogical(obj.on_ground);obj.last_on_ground=safeLogical(obj.last_on_ground);obj.last_feeding=safeLogical(obj.last_feeding);obj.last_below_desired=safeLogical(obj.last_below_desired);obj.last_alt_ft=ensureScalarDouble(obj.last_alt_ft,0);obj.below_desired=(obj.alt_ft<DESIRED_FT);obj.tookoff=false;ifobj.below_desired&&~obj.on_groundifobj.last_on_ground==true||(obj.last_feeding==false&&obj.last_alt_ft<DESIRED_FT)obj.tookoff=true;obj.takeoff_time=datetime('now');ifnargin>=2&&~isempty(airports)obj.nearestAirport=getNearestAirport(obj.latitude,obj.longitude,airports);endendendobj.landed=false;ifobj.on_ground&&~obj.last_on_ground&&obj.last_below_desired obj.landed=true;ifnargin>=2&&~isempty(airports)obj.nearestAirport=getNearestAirport(obj.latitude,obj.longitude,airports);endendobj.last_on_ground=obj.on_ground;obj.last_alt_ft=obj.alt_ft;obj.last_below_desired=obj.below_desired;endfunctionmsg=generateMessage(obj)ifobj.tookoff a=obj.nearestAirport;msg=sprintf('✈️ 起飞!从 %s (%s, %s)',a.municipality,a.region,a.iso_country);elseifobj.landed a=obj.nearestAirport;dur='';if~isempty(obj.takeoff_time)dt=datetime('now')-obj.takeoff_time;dur=sprintf(' 飞行时间 %d小时%d分',floor(dt.Hours),floor(dt.Minutes));endmsg=sprintf('🛬 降落!在 %s (%s, %s)%s',a.municipality,a.region,a.iso_country,dur);elsemsg='';endendfunctiontf=hasEvent(obj)tf=obj.tookoff||obj.landed;endfunctionobj=clearEvent(obj)obj.tookoff=false;obj.landed=false;obj.nearestAirport=[];endendendfunctionval=safeLogical(val)ifisempty(val),val=false;elseif~isscalar(val),val=val(1);endval=logical(val);endfunctionval=ensureScalarDouble(val,defaultVal)ifisempty(val)||~isscalar(val)||~isnumeric(val)val=defaultVal;elseval=double(val);endend

3. 辅助函数

文件功能说明
getOpenSkyData.m调用 OpenSky API 获取飞机状态
getNearestAirport.mHaversine 公式计算最近机场
readAirports.m读取airports.csv,自动下载
ensureDataFiles.m下载 OurAirports 公开数据集
sendDiscordNotification.mDiscord Webhook 消息推送

🎉 总结

通过这个项目,我们不仅学会了如何利用 MATLAB 调用公开的 REST API、处理 JSON 数据,还实践了面向对象编程、实时事件检测和地图可视化等实用技术。这些技能在物联网数据监控、智慧城市、应急救援等领域都有广泛的应用前景。

再次提醒:所有数据均来源于公开接口,本项目仅供技术研究与学习,请勿用于任何违法违规用途。

如果觉得有用,欢迎点赞收藏!有任何问题或改进建议,也欢迎在评论区交流讨论。


代码下载:https://download.csdn.net/download/CAINIAOdaDIDI/93388532

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/5 9:56:22

机器人关节模组编码器选型:四种多圈掉电不丢失方案对比

1. 引言在机器人关节模组的设计中&#xff0c;编码器的选择至关重要&#xff0c;尤其是对于直线关节模组。直线关节的位置计算公式为&#xff1a;位置 零点 导程 圈数。这意味着&#xff0c;机器人上电后必须能够立即获知关节的绝对位置&#xff0c;这就要求编码器具备多圈掉…

作者头像 李华
网站建设 2026/9/5 9:54:20

Codex 破局:前端组件秒级生成的技术实践

1. 引言&#xff1a;从「手写组件」到「秒级生成」的范式转变 前端开发的效率瓶颈&#xff1a;重复性组件开发、样式调试、样板代码Codex 等 AI 编程助手的崛起&#xff0c;如何改变前端工程师的日常本文目标&#xff1a;拆解 Codex 生成前端组件的完整链路、核心技巧与落地经验…

作者头像 李华
网站建设 2026/9/5 9:52:44

技术术语精准使用:提升代码质量与团队协作效率的工程实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/5 9:51:33

MCP 2026-07-28协议更新详解:AI工具集成的标准化通信桥梁

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/5 9:51:18

AI自动化游戏对局复盘:本地部署与实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/5 9:51:18

截图、图表、PDF 直接喂给模型:DMXAPI 新上架的 DeepSeek 视觉版能做什么

当开发者需要让模型理解一段复杂的前端报错截图&#xff0c;或者产品经理希望直接分析一份包含图表的竞品 PDF 时&#xff0c;传统的纯文本交互往往效率不高。现在&#xff0c;一个能直接“看懂”图片并进行分析的模型&#xff0c;正在成为提升这类工作流效率的关键工具。 Dee…

作者头像 李华