
简介面向高校计算机、遥感、航空航天等相关专业学生这份基于Python实现的卫星对地覆盖计算与任务规划项目是一份可直接运行的课设级源码适合作为课程设计、毕业设计或期末大作业的参考方案。压缩包仅5KB共包含3个文件一个Python主程序、一份项目说明txt和一份Markdown介绍文档代码与文档分离结构紧凑便于快速理解核心计算思路、运行方式和整体流程。作者在描述中说明源码已本地运行验证、功能测试无误答辩评审平均分达到97.5分完成度与可靠性较有保障。目前已有90人学习下载内容体积小但指向明确适合需要完成同类课题任务或希望在此基础上进行二次开发的学习者。通过研读这份代码可以理解卫星对地覆盖计算的基本实现方式并借鉴其任务规划流程的设计方法。1. 基于Python的卫星对地覆盖计算与任务规划为什么覆盖是“时间”问题而不是“几何”问题卫星对地覆盖计算的第一步容易让人踩坑以为算出卫星星下点、画一个可见半径圆再和地面站坐标做一次距离比较就得到了覆盖结果。实际上卫星在几百公里高度飞行角速度达到每分钟几度地面站能观测到的窗口往往只有几分钟到十几分钟覆盖范围不是“在圆内”的静态集合而是“在一段时间内满足仰角约束和时间累积”的动态序列。若按静态几何算误报会非常多——最常见的情况是地面站和卫星明明在同一经纬度附近却因为地平线遮挡或仰角低于地面站最小工作角而完全不可见。这个标题背后是一类课程设计和工程项目里高频出现的组合需求先用轨道力学算出卫星在何时何地能覆盖哪些地面目标再由任务规划模块把多条可见窗口分配给不同卫星或载荷生成可执行的观测计划。本文用Python实现一套完整最小系统从TLE两行根数出发用SGP4传播轨道、计算星下点与升交点时间再做逐秒扫描得到覆盖窗口最后用贪心调度解决多个任务争抢窗口的冲突。全流程不用商业软件装好Python环境和两个开源库即可运行。适合正在做卫星任务规划课设、想弄懂覆盖计算边界条件或者准备把这一块写进毕业设计里的读者。2. 轨道传播与坐标变换用Python把TLE变成可计算的卫星位置2.1 从TLE到ECEFSGP4模型与坐标基准的选择TLETwo-Line Element是卫星两行根数描述文件里的标准编码记录的是某一时刻的轨道根数不是实时位置。要算出任意时刻的卫星坐标必须用SGP4Simplified General Perturbations模型做传播。常见做法是调用sgp4库或skyfield前者只负责TLE解析和位置计算后者封装了坐标变换和时间系统更适合对地覆盖计算。坐标基准的选择直接决定覆盖判断是否准确。SGP4默认输出TEMETrue Equator Mean Equinox坐标系下的地心惯性位置而地面站坐标通常是WGS84经纬高。两者之间至少需要一次转换TEME先转ECI再考虑地球自转、极移和岁差章动转到ECEF或者直接把TEME转到ECEF。skyfield内部处理了这套变换下面代码用skyfield读TLE并计算卫星在ECEF下的坐标from skyfield.api import load, EarthSatellite from skyfield.framelib import itrs ts load.timescale() tle_line1 1 25544U 98067A 24001.50000000 .00010000 00000-0 15000-3 0 9990 tle_line2 2 25544 51.6400 190.0000 0004000 10.0000 350.0000 15.50000000000001 satellite EarthSatellite(tle_line1, tle_line2, ISS, ts) t ts.utc(2025, 6, 1, 12, 0, 0) geocentric satellite.at(t) pos_ecf geocentric.frame_xyz(itrs).km print(ECEF坐标(km):, pos_ecf)itrs是国际地球参考框架frame_xyz(itrs)返回的是ECEF下的坐标单位千米。这一步把卫星位置从惯性系转到地固系后续和地面站做几何判断时不用再手动处理地球自转。注意ts.utc()传入的时间要精确到秒级因为卫星速度在LEO低地球轨道下约7.5km/s差一秒就是7.5公里的位置偏差覆盖窗口边界可能差十几秒。2.2 地面站的几何表示站心坐标系与仰角计算地面站坐标用WGS84经纬度换算成ECEF再用站心坐标系ENU计算仰角。ENU的原点在地面站E轴指向东N轴指向北U轴指向天顶卫星的仰角就是U轴与视线方向的夹角。判断“是否可见”通常用最小仰角elevation_mask常见地面站取5°到10°低于这个值不考虑。下面的函数把地面站经纬高转为ECEF并计算卫星在站心坐标下的仰角import numpy as np def geodetic_to_ecef(lat_deg, lon_deg, alt_m): a 6378137.0 e2 6.69437999014e-3 lat np.deg2rad(lat_deg) lon np.deg2rad(lon_deg) N a / np.sqrt(1 - e2 * np.sin(lat) ** 2) x (N alt_m) * np.cos(lat) * np.cos(lon) y (N alt_m) * np.cos(lat) * np.sin(lon) z (N * (1 - e2) alt_m) * np.sin(lat) return np.array([x, y, z]) def elevation_angle(sat_ecef, station_ecef, lat_deg, lon_deg): r sat_ecef - station_ecef lat np.deg2rad(lat_deg) lon np.deg2rad(lon_deg) # ECEF到ENU的旋转矩阵 R np.array([ [-np.sin(lon), np.cos(lon), 0], [-np.sin(lat) * np.cos(lon), -np.sin(lat) * np.sin(lon), np.cos(lat)], [np.cos(lat) * np.cos(lon), np.cos(lat) * np.sin(lon), np.sin(lat)] ]) enu R r if np.linalg.norm(enu[:2]) 1e-6: return 90.0 if enu[2] 0 else -90.0 el np.rad2deg(np.arctan2(enu[2], np.linalg.norm(enu[:2]))) return elgeodetic_to_ecef把经纬高转为地心固连坐标elevation_angle用旋转矩阵得到ENU分量。arctan2相比arcsin更稳能正确处理仰角接近±90°的情况。station_ecef需要在循环外预先算好避免每步重复计算。2.3 轨道周期与地面轨迹的精度匹配覆盖窗口扫描的时间范围至少是一个轨道周期。LEO轨道周期约90分钟但一个周期内卫星不一定经过目标上方所以要扫描多个周期。更合理的做法是先算出每天的轨道数def orbit_period_minutes(semi_major_km, mu398600.4418): return 2 * np.pi * np.sqrt(semi_major_km ** 3 / mu) / 60 TLE_line2_fields tle_line2.split() mean_motion_rev_per_day float(TLE_line2_fields[7]) period_minutes 1440.0 / mean_motion_rev_per_day print(f轨道周期约 {period_minutes:.1f} 分钟)时间步长选10秒可以初步扫出窗口但窗口边界精度不够。更好的策略是先粗扫找窗口区间再做二分法或多分辨率扫描。这个技巧会在第5章展开。现在先把核心几何判据做完整。3. 对地覆盖计算从几何判据到可见窗口序列3.1 覆盖判断的两个必要条件仰角与地球遮挡卫星能否覆盖地面目标多数任务场景下要求两个条件同时满足仰角大于地面站最小仰角卫星与地面站的连线段不被地球遮挡。第二个条件等价于视线到地心的最近距离大于地球半径。可以先用几何快速排除——在ENU坐标下如果仰角已经大于某个阈值比如15°基本不会被地球遮挡但如果阈值很低比如0°左右必须单独判断遮挡。下面给出完整判断函数def is_visible(sat_ecef, station_ecef, lat_deg, lon_deg, min_elevation_deg10.0): el elevation_angle(sat_ecef, station_ecef, lat_deg, lon_deg) if el min_elevation_deg: return False # 判断视线是否穿过地球原点到线段的距离 r_sat np.linalg.norm(sat_ecef) r_sta np.linalg.norm(station_ecef) d sat_ecef - station_ecef t -np.dot(station_ecef, d) / np.dot(d, d) if 0 t 1: closest station_ecef t * d if np.linalg.norm(closest) 6371.0: return False return True这里t是线段上垂足对应的参数closest是视线到地心最近点。地球半径取6371km没有把大气层折射考虑进去实际工程中可以按任务要求加3到5km的余量。3.2 时间步进扫描与窗口区间提取扫描法是在时间轴上以固定步长推进对每一时刻判断可见性再合并连续可见的时间区间。步长过大可能漏掉短窗口步长过小计算量大。常见做法是先用30秒粗扫再用1秒细扫确认边界。下面的代码按1秒步长扫描并提取窗口def scan_visibility(satellite, station_ecef, lat, lon, t_start, t_end, step_s1.0): windows [] in_view False for offset in range(0, int((t_end - t_start).seconds) 1, step_s): t ts.utc(t_start.utc_datetime() timedelta(secondsoffset)) pos satellite.at(t).frame_xyz(itrs).km visible is_visible(pos, station_ecef, lat, lon) if visible and not in_view: start offset in_view True elif not visible and in_view: windows.append((start, offset - step_s)) in_view False if in_view: windows.append((start, int((t_end - t_start).seconds))) return windows返回值windows里的整数是从t_start起的偏移秒数。用timedelta(secondsoffset)转回UTC时刻就得到窗口起止时间。3.3 覆盖统计与结果表得到窗口后统计意义在于三个量总覆盖时长、覆盖次数窗口个数、平均每次窗口长度。加上仰角峰值更有参考价值def summarize_windows(satellite, station_ecef, lat, lon, t_start, windows): total 0 peak_el -90 peak_time None details [] for start, end in windows: total end - start 1 # 找窗口内最高仰角时刻 for offset in range(start, end 1): t ts.utc(t_start.utc_datetime() timedelta(secondsoffset)) pos satellite.at(t).frame_xyz(itrs).km el elevation_angle(pos, station_ecef, lat, lon) if el peak_el: peak_el el peak_time t.utc_iso() details.append({ start_s: start, end_s: end, duration_s: end - start 1, start_utc: ts.utc(t_start.utc_datetime() timedelta(secondsstart)).utc_iso(), end_utc: ts.utc(t_start.utc_datetime() timedelta(secondsend)).utc_iso() }) return {total_s: total, count: len(windows), peak_elevation: peak_el, peak_time: peak_time, windows: details}这个函数记录每个窗口的起止UTC和峰值仰角方便后续任务规划模块按观测质量排序。下面是一段某个LEO卫星对北京站39.9°N, 116.4°E, 50m的覆盖结果示例窗口号开始UTC2025-06-01结束UTC时长(秒)峰值仰角112:03:1012:15:2273254.3°214:28:4514:39:1062547.8°316:51:3017:02:5568538.2°带表格输出结果时建议直接把windows写成CSV方便和任务规划模块对接。下一步的关键问题是窗口有了多个地面站、多颗卫星的窗口互相重叠时怎么分配给具体任务。4. 任务规划模块把窗口序列编排成可执行的观测计划4.1 问题建模任务-窗口二分图与约束条件任务规划的目标是给每个待执行任务分配一个或多个可见窗口使总收益最大化同时满足约束一个窗口只能分配给一个任务每个任务至少执行一次某些任务必须重复执行N次载荷工作时间有最大累计上限。这是一个典型的时间窗约束指派问题。课设规模下不需要上商用求解器用贪心算法加冲突消解就能得到可用的计划。建模方式遍历所有窗口对每个窗口检查哪些任务在这个窗口内可执行任务的可执行条件通常是目标区域在视场内生成“窗口-任务”候选集合。收益直接用峰值仰角加权。4.2 贪心调度按窗口长度排序分配贪心策略按窗口长度从小到大排序优先分配短窗口把长窗口留给其他任务。原因很简单短窗口可替代性低。代码如下def greedy_schedule(windows, tasks, max_duration_per_task600): # windows: [(start_s, end_s, peak_el)] # tasks: [{id:1, name:成像, required:1}, ...] assigned [] task_remaining {t[id]: t[required] for t in tasks} task_used_time {t[id]: 0 for t in tasks} sorted_windows sorted(windows, keylambda w: w[1] - w[0]) for w in sorted_windows: start, end, peak w duration end - start 1 # 找出剩余需求最大且时间允许的任务 best_task None best_score -1 for t in tasks: tid t[id] if task_remaining[tid] 0: continue if task_used_time[tid] duration max_duration_per_task: continue score peak * t.get(priority, 1.0) if score best_score: best_score score best_task tid if best_task is not None: assigned.append((best_task, start, end)) task_remaining[best_task] - 1 task_used_time[best_task] duration return assignedmax_duration_per_task是每个任务累计占用时间的上限防止某个任务把载荷的整个寿命耗光。task_remaining控制重复次数peak作为收益权重天然偏向高仰角窗口。这个算法复杂度是O(W×T)W是窗口数T是任务数课设规模下几千个窗口、几十个任务完全没问题。4.3 冲突消解窗口重叠时如何取舍多任务同时争抢同一窗口时贪心法可能会先到先得导致后面高优先级任务没窗口可用。解决方案是预排序时不只按窗口长度还要乘以任务优先级或者分两轮第一轮给高优先级任务第二轮给普通任务。下面这段代码实现了优先级分层def two_pass_schedule(windows, tasks, high_priority_ids): result [] sorted_windows sorted(windows, keylambda w: (w[1] - w[0], -w[2])) tasks_by_level [ [t for t in tasks if t[id] in high_priority_ids], [t for t in tasks if t[id] not in high_priority_ids] ] for level_tasks in tasks_by_level: for start, end, peak in sorted_windows: if any(s start and e end for _, s, e in result): continue for t in level_tasks: if t[required] 0 and (end - start 1) t.get(max_duration, 600): result.append((t[id], start, end)) t[required] - 1 break return result这里的any(s start and e end ...)用来检查窗口是否已被占用。分两轮保证高优先级任务不会低优先级任务抢掉窗口。4.4 输出甘特图与调度评价调度结果最后要能直接出图。用matplotlib画横向甘特图X轴是UTC时间Y轴是任务ID每个窗口画一个色块import matplotlib.pyplot as plt from datetime import datetime, timedelta def plot_schedule(assignments, base_time, save_pathschedule.png): fig, ax plt.subplots(figsize(12, 6)) for task_id, start, end in assignments: start_dt base_time timedelta(secondsstart) end_dt base_time timedelta(secondsend) ax.barh(task_id, (end_dt - start_dt).total_seconds(), leftstart_dt, height0.6, labelftask {task_id}) ax.set_xlabel(UTC time) ax.set_ylabel(Task ID) ax.invert_yaxis() plt.tight_layout() plt.savefig(save_path)调度评价指标至少看三项任务完成率被分配窗口的任务数/总任务数、窗口利用率已分配窗口总时长/所有窗口总时长、平均峰值仰角。这些指标在课设答辩里都是加分的量化结果。5. 进阶技巧二分法精确定位窗口边界避免短窗口漏算5.1 粗扫漏窗口的场景第3章的逐秒扫描在步长是1秒时窗口边界误差最多1秒。但有些高轨卫星可见窗口长达几十分钟粗扫用10秒步长会带来很大偏差有些低轨卫星对高仰角约束的地面站可见窗口短到只有几秒1秒步长正好卡在边界上会漏检。以5°最小仰角为例若粗扫步长恰好覆盖窗口的起止边界处仰角变化率约每秒0.05°到0.2°1秒误差可接受。但若窗口本身只有3秒粗扫步长30秒可能完全错过。解决方法是先用大步长粗扫对探测到“可见”状态变化的相邻时间点做二分法精确找仰角等于min_elevation_deg的时刻。5.2 二分法找精确的进入/退出时间实现思路是维护一个时间区间[t0, t1]t0时刻不可见t1时刻可见两者相差一个粗扫步长。在这个区间内反复取中点判断中点时刻的可见性把不可见边界向前推进直到区间长度小于0.1秒def find_boundary(satellite, station_ecef, lat, lon, t0, t1, tolerance0.1): # t0不可见, t1可见, 返回精确边界 while (t1 - t0).seconds tolerance: t_mid ts.utc(t0.utc_datetime() (t1.utc_datetime() - t0.utc_datetime()) / 2) pos satellite.at(t_mid).frame_xyz(itrs).km if is_visible(pos, station_ecef, lat, lon, min_elevation_deg): t1 t_mid else: t0 t_mid return t1调用时对粗扫结果中每个窗口的首末时间点都要执行一次find_boundary。注意(t1 - t0).seconds不能处理跨天的情况稳妥做法是直接用UTC时间戳差值from datetime import timezone def utc_timestamp(dt): return dt.replace(tzinfotimezone.utc).timestamp()5.3 多分辨率组合粗扫二分法的完整流程完整流程分三步以20秒步长粗扫得到阶段性的连续可见区间对每个区间的起点用二分法向左收敛对终点向右收敛最后合并间隔小于5秒的相邻窗口。第二步的关键是粗扫的边界判定条件要用is_visible含仰角遮挡判断而不是单独看仰角否则二分法收敛的是仰角边界而非几何可见边界。短窗口漏算的另一个常见原因是时间源没有对齐。skyfield内部使用TAI时间连续流转程序的计时循环用的是系统的挂钟时间两者在跨闰秒时可能差1秒。这个误差在多个窗口累积后可能错开边界导致任务规划阶段把一个连续窗口当成两个短窗口。解决方法是整个程序里只用ts.utc()和timedelta计算不混用time.time()。5.4 验证技巧用反证法测试覆盖计算正确性把地面站放在卫星星下点正下方仰角应接近90°放在卫星地面轨迹经过的位置但仰角约束设为90°窗口长度应为0。还有一种验证让地面站坐标等于卫星星下点连续计算1小时应看到仰角从低到高再降低的抛物线变化。下面这行代码用来检查仰角序列的峰值是否出现在星下点最接近的时刻# 假设peak_el_time已知检查该时刻星下点到地面站距离是否最小 from skyfield.api import wgs84 subpoint satellite.at(t_peak).subpoint() lat_diff abs(subpoint.latitude.degrees - station_lat) lon_diff abs(subpoint.longitude.degrees - station_lon) print(lat_diff, lon_diff)至于任务规划结果验证手段是把同一组窗口给两个不同调度策略贪心与两轮贪心跑一遍比较完成率再手动抽查一个被分配的任务确保它的执行窗口确实落在对应地面站的可见区间内。若两种策略结果相同那大概率是窗口本身没有冲突算法选谁都一样若不同则说明冲突确实存在优先级策略生效了。本文还有配套的精品资源点击获取
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。