北斗全球电离层延迟修正模型(BDGIM)的广播系数是北斗卫星导航系统实时向用户播发的电离层延迟改正参数,具有以下特点:
-
每个BDGIM预报包含9个球谐函数系数,这些系数基于球谐函数展开模型,能够精确描述全球电离层总电子含量(TEC)的空间分布特征。
-
系统每2小时更新并播发一组新的系数,每天共播发12组系数,确保电离层改正信息的时效性。
-
系数采用科学计数法表示,格式为±X.XXXXXXXXXXXXe±XX,提供15位有效数字的精度,如2.787500000000e+01表示27.875。
-
系数通过北斗卫星的导航电文(D2电文)广播,嵌入在BRD400DLR_S开头的广播星历文件中,以"> ION CXX CNVX"标识符开头。
1、系数下载来源
武汉大学GNSS数据中心,下载链接类似如下:
ftp://igs.gnsswhu.cn/pub/gps/data/daily/2025/brdc/BRD400DLR_S_20251890000_01D_MN.rnx.gz
-
文件命名规范:BRD400DLR_S_YYYYDOYHHMM_01D_MN.rnx.gz
-
YYYY:年份
-
DOY:年积日(001-366)
-
HHMM:起始时间
-
-
解压处理:需确保文件已从.gz格式解压为.rnx格式
-
编码格式:文件通常为ASCII或UTF-8编码
2、数据批处理下载代码:
python
import os
import ftplib
import datetime
import time
import logging
# 设置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
logger = logging.getLogger(__name__)
class GNSSDownloader:
def __init__(self, ftp_host="igs.gnsswhu.cn"):
"""初始化下载器"""
self.ftp_host = ftp_host
self.ftp = None
def connect_ftp(self):
"""连接到FTP服务器"""
try:
self.ftp = ftplib.FTP(self.ftp_host, timeout=30)
self.ftp.login() # 匿名登录
logger.info(f"成功连接到FTP服务器: {self.ftp_host}")
return True
except Exception as e:
logger.error(f"连接FTP服务器失败: {e}")
return False
def disconnect_ftp(self):
"""断开FTP连接"""
if self.ftp:
try:
self.ftp.quit()
except:
self.ftp.close()
logger.info("已断开FTP连接")
def date_to_doy(self, date_obj):
"""将日期转换为年积日(DOY)"""
year = date_obj.year
doy = date_obj.timetuple().tm_yday
return year, doy
def generate_filename(self, year, doy):
"""生成文件名"""
# 格式: BRD400DLR_S_20220060000_01D_MN.rnx.gz
doy_str = f"{doy:03d}"
filename = f"BRD400DLR_S_{year}{doy_str}0000_01D_MN.rnx.gz"
return filename
def generate_remote_path(self, year, doy):
"""生成远程文件路径"""
# 示例: /pub/gps/data/daily/2022/brdc/
remote_path = f"/pub/gps/data/daily/{year}/brdc/"
return remote_path
def create_local_dir(self, year):
"""创建本地保存目录 - 只按年份创建目录"""
local_dir = os.path.join("downloaded_data", str(year))
os.makedirs(local_dir, exist_ok=True)
return local_dir
def download_single_file(self, year, doy, retry_count=3):
"""下载单个文件"""
# 生成文件名
filename = self.generate_filename(year, doy)
# 远程路径
remote_path = self.generate_remote_path(year, doy)
# 本地保存路径 - 直接放在年份目录下
local_dir = self.create_local_dir(year)
local_path = os.path.join(local_dir, filename)
# 检查文件是否已存在
if os.path.exists(local_path):
file_size = os.path.getsize(local_path)
if file_size > 1024: # 文件大于1KB才认为有效
logger.info(f"文件已存在,跳过: {filename} ({file_size} bytes)")
return True, local_path
# 尝试下载文件
for attempt in range(retry_count):
try:
logger.info(f"正在下载: {filename} (尝试 {attempt + 1}/{retry_count})")
# 切换到远程目录
self.ftp.cwd(remote_path)
# 获取文件大小
file_size = None
try:
self.ftp.voidcmd(f"SIZE {filename}")
file_size = int(self.ftp.sendcmd(f"SIZE {filename}").split()[1])
except:
pass
# 下载文件
with open(local_path, 'wb') as f:
self.ftp.retrbinary(f'RETR {filename}', f.write)
# 验证下载
if os.path.exists(local_path):
actual_size = os.path.getsize(local_path)
if actual_size > 0:
if file_size and actual_size == file_size:
logger.info(f"下载成功: {filename} ({actual_size} bytes, 完整)")
else:
logger.info(f"下载成功: {filename} ({actual_size} bytes)")
return True, local_path
else:
logger.warning(f"下载的文件大小为0: {filename}")
os.remove(local_path)
except ftplib.error_perm as e:
if "550" in str(e): # 文件不存在
logger.warning(f"文件不存在: {filename}")
return False, None
logger.error(f"权限错误: {filename} - {e}")
except Exception as e:
logger.error(f"下载失败 {filename}: {e}")
# 重试前等待
if attempt < retry_count - 1:
time.sleep(2)
logger.error(f"下载失败,已达到最大重试次数: {filename}")
return False, None
def download_date_range(self, start_date, end_date):
"""
下载指定日期范围的数据
Args:
start_date: 开始日期,格式 'YYYY-MM-DD' 或 datetime对象
end_date: 结束日期,格式 'YYYY-MM-DD' 或 datetime对象
"""
# 转换日期格式
if isinstance(start_date, str):
start_date = datetime.datetime.strptime(start_date, "%Y-%m-%d")
if isinstance(end_date, str):
end_date = datetime.datetime.strptime(end_date, "%Y-%m-%d")
# 连接到FTP服务器
if not self.connect_ftp():
logger.error("无法连接到FTP服务器,程序退出")
return
try:
# 遍历每一天
current_date = start_date
success_count = 0
fail_count = 0
missing_files = []
logger.info(f"开始下载 {start_date.date()} 到 {end_date.date()} 的数据")
logger.info(f"文件将保存在: downloaded_data/[年份]/ 目录下")
while current_date <= end_date:
year, doy = self.date_to_doy(current_date)
date_str = current_date.strftime("%Y-%m-%d")
logger.info(f"处理: {date_str} (Year: {year}, DOY: {doy})")
# 下载文件
success, local_path = self.download_single_file(year, doy)
if success:
success_count += 1
else:
fail_count += 1
missing_files.append(date_str)
# 移动到下一天
current_date += datetime.timedelta(days=1)
# 短暂延迟,避免请求过快
time.sleep(0.5)
# 下载结果统计
logger.info("=" * 50)
logger.info(f"下载完成!")
logger.info(f"成功: {success_count} 个文件")
logger.info(f"失败: {fail_count} 个文件")
logger.info(f"文件保存位置: downloaded_data/ 目录下")
if missing_files:
logger.info("缺失的文件日期:")
for date in missing_files:
logger.info(f" - {date}")
except KeyboardInterrupt:
logger.info("用户中断下载")
except Exception as e:
logger.error(f"下载过程中发生错误: {e}")
finally:
self.disconnect_ftp()
def download_specific_years(self, years):
"""
下载特定年份的数据
Args:
years: 年份列表,如 [2022, 2023, 2024]
"""
for year in years:
logger.info(f"开始下载 {year} 年的数据")
# 设置该年的开始和结束日期
start_date = datetime.datetime(year, 1, 1)
# 判断是否为闰年
if (year % 4 == 0 and year % 100 != 0) or (year % 400 == 0):
end_date = datetime.datetime(year, 12, 31)
else:
end_date = datetime.datetime(year, 12, 31)
# 下载该年份的数据
self.download_date_range(start_date, end_date)
def download_specific_dates(self, date_list):
"""
下载特定日期的数据
Args:
date_list: 日期列表,格式 ['YYYY-MM-DD', 'YYYY-MM-DD', ...]
"""
if not self.connect_ftp():
logger.error("无法连接到FTP服务器,程序退出")
return
try:
success_count = 0
fail_count = 0
for date_str in date_list:
try:
date_obj = datetime.datetime.strptime(date_str, "%Y-%m-%d")
year, doy = self.date_to_doy(date_obj)
logger.info(f"处理: {date_str} (Year: {year}, DOY: {doy})")
success, _ = self.download_single_file(year, doy)
if success:
success_count += 1
else:
fail_count += 1
except ValueError:
logger.error(f"日期格式错误: {date_str}")
fail_count += 1
# 短暂延迟
time.sleep(0.5)
logger.info(f"特定日期下载完成: 成功 {success_count}, 失败 {fail_count}")
finally:
self.disconnect_ftp()
def check_existing_files(self, year=None):
"""
检查已下载的文件
Args:
year: 要检查的年份,如果为None则检查所有年份
"""
base_dir = "downloaded_data"
if not os.path.exists(base_dir):
logger.info(f"下载目录不存在: {base_dir}")
return
if year:
# 检查特定年份
year_dir = os.path.join(base_dir, str(year))
if os.path.exists(year_dir):
files = os.listdir(year_dir)
gnss_files = [f for f in files if f.startswith("BRD4")]
logger.info(f"{year}年已下载 {len(gnss_files)} 个文件:")
for file in sorted(gnss_files)[:10]: # 只显示前10个文件
logger.info(f" - {file}")
if len(gnss_files) > 10:
logger.info(f" ... 还有 {len(gnss_files) - 10} 个文件")
else:
logger.info(f"{year}年目录不存在")
else:
# 检查所有年份
years = [d for d in os.listdir(base_dir) if os.path.isdir(os.path.join(base_dir, d))]
logger.info("已下载的年份:")
for year_dir in sorted(years):
year_path = os.path.join(base_dir, year_dir)
if os.path.isdir(year_path):
files = os.listdir(year_path)
gnss_files = [f for f in files if f.startswith("BRD4")]
logger.info(f" {year_dir}: {len(gnss_files)} 个文件")
def main():
"""主函数 - 下载数据"""
# 创建下载器实例
downloader = GNSSDownloader()
# 设置下载日期范围
start_date = "2025-01-01"
end_date = "2025-12-31"
# 开始下载
downloader.download_date_range(start_date, end_date)
# 检查已下载的文件
downloader.check_existing_files()
if __name__ == "__main__":
print("北斗广播星历文件下载工具")
main()
3、提取广播系数

-
标识符定位 :系数块以
> ION CXX CNVX开头-
CXX中的XX为卫星编号(如C29、C19等)
-
CNVX表示电离层改正参数
-
-
块结构:每个块包含3行数据
-
第1行:时间戳 + 前3个系数
-
第2行:中间4个系数
-
第3行:最后2个系数
-
-
科学计数法:所有系数均为科学计数法格式
-
格式:
±X.XXXXXXXXXXXXe±XX -
示例:
2.787500000000e+01
-
-
无间隔系数:注意系数间可能没有空格
-
正确解析:
2.787500000000e+01-4.500000000000e+00 -
应分割为:
2.787500000000e+01和-4.500000000000e+00
-
-
系数数量:每个时间点固定为9个系数
4、汇总处理结果
汇总后发现有些历元竟然有不同的广播系数,播发间隔也是五花八门,不是固定的时间间隔,查看原文件确实如此,不太懂是什么原因,求解答。
