北斗电离层模型BDGIM广播系数

北斗全球电离层延迟修正模型(BDGIM)的广播系数是北斗卫星导航系统实时向用户播发的电离层延迟改正参数,具有以下特点:

  1. 每个BDGIM预报包含9个球谐函数系数,这些系数基于球谐函数展开模型,能够精确描述全球电离层总电子含量(TEC)的空间分布特征。

  2. 系统每2小时更新并播发一组新的系数,每天共播发12组系数,确保电离层改正信息的时效性。

  3. 系数采用科学计数法表示,格式为±X.XXXXXXXXXXXXe±XX,提供15位有效数字的精度,如2.787500000000e+01表示27.875。

  4. 系数通过北斗卫星的导航电文(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、汇总处理结果

汇总后发现有些历元竟然有不同的广播系数,播发间隔也是五花八门,不是固定的时间间隔,查看原文件确实如此,不太懂是什么原因,求解答。

相关推荐
还在忙碌的吴小二1 天前
Harness 最佳实践:Java Spring Boot 项目落地 OpenSpec + Claude Code
java·开发语言·spring boot·后端·spring
liliangcsdn1 天前
mstsc不在“C:\Windows\System32“下在C:\windows\WinSxS\anmd64xxx“问题分析
开发语言·windows
weixin_156241575761 天前
基于YOLOv8深度学习花卉识别系统摄像头实时图片文件夹多图片等另有其他的识别系统可二开
大数据·人工智能·python·深度学习·yolo
AI_Claude_code1 天前
ZLibrary访问困境方案三:Web代理与轻量级转发服务的搭建与优化
爬虫·python·web安全·搜索引擎·网络安全·web3·httpx
小陈工1 天前
2026年4月7日技术资讯洞察:下一代数据库融合、AI基础设施竞赛与异步编程实战
开发语言·前端·数据库·人工智能·python
KAU的云实验台1 天前
【算法精解】AIR期刊算法IAGWO:引入速度概念与逆多元二次权重,可应对高维/工程问题(附Matlab源码)
开发语言·算法·matlab
时空无限1 天前
ansible 由于不同主机 python 版本不同执行报错
python·ansible
会编程的土豆1 天前
【数据结构与算法】再次全面了解LCS底层
开发语言·数据结构·c++·算法
ZhengEnCi1 天前
P2E-Python字典操作完全指南-从增删改查到遍历嵌套的Python编程利器
python
alanesnape1 天前
使用AVL平衡树和列表实现 map容器 -- 附加测试/python代码
python·map·avl 平衡树·bst树·二叉树旋转