GVF观测数据格式说明
GVF 是一种面向天体测量和大地测量 VLBI 的观测数据格式。在 pSolve 中,它主要以二进制形式保存数据,文件扩展名为 .bgv。其中不仅包含群时延、时延率等观测量,还可以保存测站和射电源信息、校准数据、理论计算结果,以及解算设置和处理状态。参照Leonid写的说明文档,并结合目前的程序代码,我在这里记录下 GVF 数据库的文件组织方式。
一、GVF 格式
一次观测的 GVF 数据库通常由多个文件组成。各文件分别保存一部分数据,再由一个 .env 文件列出所使用的数据文件及其版本。例如,一个数据文件可以命名为:
20230930_j_c23273_fr1_v001.bgv
其中,20230930 是日期,j 是数据库后缀,c23273 是观测编号,fr1 表示数据分段,v001 表示该文件的版本。
按照 Leonid 的数据库命名习惯,常规 24 小时观测常用 a、b、c 等后缀,UT1 加强观测常用 i、j、k 等后缀。后缀 p 表示 Leonid 使用 PIMA 重新进行相关后处理、完成条纹拟合后生成的版本,与 IVS/CDDIS 发布的采用 FOURFIT 进行条纹拟合的版本相对应。因此,后缀也反映了数据的处理来源。
常见的数据分段包括:
| 分段 | 主要内容 |
|---|---|
fr1 |
基本观测信息、群时延、时延率、信噪比等 |
fr2 |
条纹拟合和通道相关的补充信息,也可能包含系统温度、相位校准等数据 |
cl1 |
气象、电缆时延等校准信息 |
sl1 |
解算设置、观测剔除标志及其他处理状态 |
th1 |
理论计算相关信息 |
具体哪些数据放在哪个分段,由生成程序决定。这些名称可以帮助理解文件的大致用途,详细内容还需要查看其中的数据字段。这种组织方式允许各部分分别保存和更新。例如,修改观测剔除标志后,可以更新相应的解算数据分段,而不必重写所有观测数据。不同分段也可以具有不同的版本号,由 .env 文件记录它们之间的组合关系。
二进制文件适合程序读取,但不便于直接查看。为了理解数据库中究竟保存了什么,我主要从它可以导出的文本格式 VDA 入手。
二、VDA 格式
VDA 是 VGOSDA 的简称,是用于保存和交换天体测量与大地测量 VLBI 数据的文本格式。它以群时延、时延率等 L2 观测数据为核心,同时保存描述一次观测所需的辅助信息,以及数据分析过程中产生的设置和状态。
在 pSolve 中,VDA 可以与二进制 GVF 进行转换。一份 VDA 文件可以汇集一次观测的多个数据分段,便于传输,也可以直接用文本编辑器打开。因此,对我来说,VDA 既是一种数据交换格式,也是理解数据内容的入口。这是我喜欢VDA格式的原因。
2.1 文件结构
在 pSolve 中,可以使用 gvf_transform -to_ascii 将二进制 GVF 数据库导出为 VDA。程序内部由 GVH_WRITE_AGV (源代码位于gvh/gvh/gvh_write_agv.f) 写出文本文件,当前使用的格式标识为:
VGOSDA Format of 2019.09.09
一份 VDA 文件可以包含一个或多个数据块。每个数据块包含以下内容:
| 标识 | 内容 |
|---|---|
FILE.n |
该数据块对应的原始文件名 |
PREA.n |
生成程序、创建时间等前导信息 |
TEXT.n |
处理历史、相关报告等附加文本 |
TOCS.n |
数据字段的名称、类别、类型、维度和说明 |
DATA.n |
数据值;对于变长数组,还包含相应的维度信息 |
HEAP.n |
变长数组的数据;没有这类数据时为空 |
CHUN.n |
数据块的记录数信息 |
其中,n 是文件中存储数据块的编号。从二进制 GVF 导出时,每个数据块通常对应一个 .bgv 文件。例如,软件自带的观测数据库 psolve/data/20230930_j.vda.bz2 ,内部包含五个数据块:
| 数据块编号 | 对应分段 |
|---|---|
| 1 | fr1 |
| 2 | fr2 |
| 3 | cl1 |
| 4 | sl1 |
| 5 | th1 |
其中的两条文件记录为:
FILE.1 /l2/gvf/int/db/20230930_j_c23273_fr1_v001.bgv
FILE.3 /l2/gvf/int/db/20230930_j_c23273_cl1_v001.bgv
第一数据块来自 fr1 文件,第三数据块来自 cl1 文件。因此,TOCS.3 和 DATA.3 都属于第三数据块,其中可以保存多个测站在许多观测段中的校准数据。编号与分段的对应关系,由当前文件中的 FILE.n 记录给出。
为了理解数据块与观测层次之间的关系,可以将几个概念放在一起:
| 概念 | 在 VDA 中的对应关系 |
|---|---|
| session | 一次完整观测;一份 VDA 通常保存这次观测的数据库 |
数据块 n |
数据库中的一个存储分段,其来源由 FILE.n 给出 |
| scan | 一个观测段;相关数据通过 SCA 类字段和观测段索引保存 |
| observation | 一条基线观测记录;相关数据通过 BAS 类字段和记录序号保存 |
| observable | 群时延、相位时延率等观测量,保存在相应的 LCODE 字段中 |
当前写出程序会在每个数据块开始时写入格式标识,因此同一文件中可以多次出现 VGOSDA Format of 2019.09.09。
查看文件时,我主要关注 TOCS 和 DATA:前者定义字段,后者保存字段的具体值。
2.2 LCODE与数据类别
理解 VDA 的数据组织,最重要的概念是 LCODE。可以把它理解为一个有名称、有类型、有维度、有适用范围的数据字段标识。例如,GR_DELAY 保存群时延,SITNAMES 保存测站名称表。程序内部用长度为 8 个字符的名称保存这些标识,并通过名称访问相应的数据。每个字段的适用范围由其类别确定:
| 类别 | 数据对应的范围 | 字段示例 |
|---|---|---|
SES |
整次观测 | SITNAMES:测站名称表;SOU_COOR:射电源坐标 |
SCA |
一个观测段 | MJD_OBS、UTC_OBS:观测段的时间信息 |
STA |
某测站在一个观测段中的数据 | ATM_PRES:气压;AIR_TEMP:气温 |
BAS |
一条基线观测记录 | GR_DELAY:群时延;GRDELERR:群时延的形式误差;SNRATIO:信噪比 |
这种分类可以减少重复。例如,同一个观测段中,某站的气压只需保存一次,涉及该站的多条基线观测都可以使用这个值。程序通过基线观测、观测段和测站之间的索引关系找到对应数据。字段类别取决于数据的组织方式。例如,SITNAMES 属于 SES 类,因为整张测站名称表由这次观测共同使用;ATM_PRES 属于 STA 类,因为各测站的气压随观测段变化,需要分别保存。
一个 LCODE 可以保存许多组数据,每组数据又可以包含多个元素。例如,GR_DELAY 保存所有基线观测的群时延;如果有两个波段,每条基线观测就对应两个群时延。因此,定位一个具体数值需要两层信息:先确定它属于哪条基线观测记录,再确定它是该记录中哪个波段的群时延。
VDA 用四个索引表达这种关系。对于普通的定长数值字段,可以在逻辑上将其写成:
A(i1, i2, i3, i4)
其中:
i1、i2定位一组数据内部的元素;i3、i4定位这组数据对应的观测记录、观测段或测站。
TOCS 给出每组数据的前两维大小,记录形式为:
TOCS.n LCODE 类别 类型 维度1 维度2 说明
DATA 则按照先定位观测对象、再定位数组元素的顺序书写:
DATA.n LCODE i3 i4 i1 i2 数值
其中,i3 和 i4 的含义由字段类别决定:
| 类别 | i3 |
i4 |
|---|---|---|
SES |
不使用,写 0 |
不使用,写 0 |
SCA |
观测段序号 | 不使用,写 0 |
STA |
该测站参与观测段的站内序号 | 测站序号 |
BAS |
基线观测记录序号 | 不使用,写 0 |
SES 字段由整次观测共同使用,所以这两个位置都写 0。SCA 和 BAS 字段分别用一个序号确定观测段或基线观测记录。STA 字段则同时使用测站序号和站内观测段序号。这里保留两个对象索引,是为了容纳测站类数据所需要的两个编号:测站序号和该站参与的观测段序号。各类数据都保留这两个位置,不使用的位置以 0 占位,从而采用相同的记录形式。
后面的两个元素索引用于定位这一组数据内部的数值。标量的维度为 (1,1);双频群时延可以定义为 (2,1);每个通道的复相关数据则可以定义为 (2,通道数),第一维分别保存实部和虚部。
这样,四个索引就分别确定了数据所属的观测对象,以及数值在该对象内部数组中的位置。
2.3 观测段、测站与基线观测之间的关系
当前读取程序使用六个基础字段建立数据之间的对应关系:
| 字段 | 含义 |
|---|---|
NUMB_OBS |
基线观测记录总数 |
NUMB_SCA |
观测段总数 |
NUMB_STA |
测站总数 |
SITNAMES |
测站名称表 |
NOBS_STA |
各测站在数据库中参与的观测段数 |
OBS_TAB |
每条基线观测对应的观测段和两端测站 |
其中,OBS_TAB 是一个大小为 (3, NUMB_OBS) 的数组。对于第 k 条基线观测记录:
OBS_TAB(1,k):观测段序号
OBS_TAB(2,k):第一测站在测站名称表中的序号
OBS_TAB(3,k):第二测站在测站名称表中的序号
不同数据都是通过以上的索引建立关联。
2.4 实际样例
下面结合原始样例文件 20180102_r1823.agv说明这些信息是如何串联在一起的。
2.4.1 群时延数据
以数据文件中的第 3 条基线观测记录为例,用一段双频数据说明字段定义与数据值之间的关系:
TOCS.1 GR_DELAY BAS R8 2 1 Group delays per band (sec)
DATA.1 GR_DELAY 3 0 1 1 -5.320770260597998D-03
第一行定义了 GR_DELAY 字段:
| 内容 | 含义 |
|---|---|
TOCS.1 |
第一数据块中的字段定义 |
GR_DELAY |
字段名 |
BAS |
数据按基线观测记录保存 |
R8 |
8 字节双精度实数 |
2 1 |
每条基线观测对应一个大小为 (2, 1) 的数组 |
Group delays per band (sec) |
字段说明,指出这里保存各波段的群时延,单位为秒 |
在这个双频样例中,数组的两个元素分别对应两个波段:
(1, 1)保存第一波段的群时延;(2, 1)保存第二波段的群时延。
第二行中的 DATA 记录可以拆解为:
| 内容 | 含义 |
|---|---|
DATA.1 |
第一数据块中的数据 |
GR_DELAY |
群时延字段 |
第一个整数 3 |
第 3 条基线观测记录,记为 k=3 |
第二个整数 0 |
BAS 类不使用这个索引位置,本文件以 0 填写 |
后两个整数 1 1 |
数组元素索引 (1,1),这里对应第一波段 |
| 最后的数值 | 群时延,单位为 s |
因此,这一行保存的是第3条基线观测在第一波段的群时延:
\(\tau_{\mathrm{g},1}
=-5.320770260597998\times10^{-3}\ \mathrm{s}
\approx-5.320770261\ \mathrm{ms}.\)
其中,D-04 是 Fortran 双精度数常用的指数写法。同一条基线观测的第二波段群时延保存在:
DATA.1 GR_DELAY 3 0 2 1 -5.320770244317517D-03
两行的基线观测序号都是 3,变化的是波段对应的元素索引。这里的 k=3 是基线观测记录的序号。同一对测站在不同观测段形成的观测,会对应不同的记录序号。如果要表示第 25 条基线观测在第二波段的群时延,索引部分就写成:
DATA.1 GR_DELAY 25 0 2 1 ...
波段的具体含义可以结合频率字段确定。在这个样例中,第一、第二波段的参考频率分别为 8.22099 GHz 和 2.22299 GHz,对应 X 波段和 S 波段。由此可见,TOCS 中的两个数字给出每组数据的数组大小,DATA 中的四个数字共同确定一个具体数值的位置。
2.4.2 从基线观测找到观测段
群时延按基线观测保存,而观测时间按观测段保存。要查找这条群时延对应的历元,需要先通过 OBS_TAB 找到它所属的观测段。OBS_TAB 的定义为:
TOCS.1 OBS_TAB SES I4 3 3396 Observation tables: scan index, indices of the first and the second station
它是一个大小为 (3,3396) 的整数数组,每一列对应一条基线观测:
| 数组元素 | 含义 |
|---|---|
OBS_TAB(1,k) |
该条基线观测所属的观测段序号 |
OBS_TAB(2,k) |
第一测站序号 |
OBS_TAB(3,k) |
第二测站序号 |
对于第 k=3 条基线观测,文件中的记录为:
DATA.1 OBS_TAB 0 0 1 3 1
DATA.1 OBS_TAB 0 0 2 3 1
DATA.1 OBS_TAB 0 0 3 3 7
这三行共同给出:
OBS_TAB(:,3) = (1, 1, 7)
因此,第 3 条基线观测属于第 1 个观测段,记为 s=1。两端测站的序号分别为 1 和 7,稍后再用它们查询基线。
接下来,读取第 1 个观测段的日期、日内秒数和名称:
TOCS.1 MJD_OBS SCA I4 1 1 MJD of fringe reference time at pseudo-UTC timecale for the scan (days)
TOCS.1 UTC_OBS SCA R8 1 1 Pseudo-UTC time tag of fringe reference time for the scan (sec)
TOCS.1 SCANNAME SCA C1 16 1 Scan name
DATA.1 MJD_OBS 1 0 1 1 58120
DATA.1 UTC_OBS 1 0 1 1 6.124400000000000D+04
DATA.1 SCANNAME 1 0 1 1 002-1700a
这三个字段属于 SCA 类,按观测段保存,字段名之后的第一个整数表示观测段序号。MJD_OBS 和 UTC_OBS 各保存一个数值,SCANNAME 保存一个长度为 16 个字符的观测段名称。它们给出的信息是:
MJD_OBS = 58120,对应日期 2018 年 1 月 2 日;UTC_OBS = 61244 s,对应日内时刻 17:00:44;SCANNAME = 002-1700a,是这个观测段的名称。 其中: \(61244=17\times3600+0\times60+44.\) 所以,按文件中的 pseudo-UTC 时间标记,这条观测的条纹参考时刻为 2018 年 1 月 2 日 17:00:44。这里给出的是条纹参考时刻,不能直接当作观测段的开始时刻。
本例中,基线观测序号是 3,观测段序号是 1。因此,不能直接用 GR_DELAY 中的序号 3 去查询 MJD_OBS 和 UTC_OBS,必须先经过 OBS_TAB 的对应关系。
2.4.3 从观测段找到射电源名称和坐标
确定观测段序号 s=1 后,通过 SOU_IND 查找这个观测段对应的射电源:
TOCS.1 SOU_IND SCA I4 1 1 Source name index
DATA.1 SOU_IND 1 0 1 1 2
SOU_IND 属于 SCA 类。这条记录表示:第 1 个观测段观测的是源名称表中的第 2 个射电源,记为 q=2。随后读取 SRCNAMES 的第 2 项:
TOCS.1 SRCNAMES SES C1 8 68 Source names
DATA.1 SRCNAMES 0 0 1 2 0017+200
由此得到射电源名称 0017+200。如果需要源坐标,可以继续读取 SOU_COOR 的第 2 列:
TOCS.1 SOU_COOR SES R8 2 68 Source coordinates in a baricenteric reference system: right asc. decl. (rad)
DATA.1 SOU_COOR 0 0 1 2 8.565599653890889D-02
DATA.1 SOU_COOR 0 0 2 2 3.553957939493710D-01
第一行给出赤经,第二行给出赤纬,单位均为 rad:
\(\alpha=0.08565599653890889\ \mathrm{rad},
\qquad
\delta=0.3553957939493710\ \mathrm{rad}.\)
这里,SRCNAMES 和 SOU_COOR 都属于 SES 类,是整次观测共用的表。SOU_IND 将各个观测段与这些表中的具体条目联系起来。
2.4.4 从基线到两端测站
测站类字段同时使用测站序号和站内观测段序号。站内观测段序号按照该站在数据库中参与的观测段依次排列。前面已经得到:
OBS_TAB(:,3) = (1, 1, 7)
其中,第二个元素表示第一测站的序号为 1,第三个元素表示第二测站的序号为 7。查找测站名称表中的对应条目:
TOCS.1 SITNAMES SES C1 8 8 IVS site names
DATA.1 SITNAMES 0 0 1 1 FORTLEZA
DATA.1 SITNAMES 0 0 1 7 WETTZ13N
因此,第 3 条基线观测对应 FORTLEZA–WETTZ13N 基线,其中第一站是 FORTLEZA,第二站是 WETTZ13N。样例中的 STA_IND 也保存了相同的两端测站序号,可以用于核对:
TOCS.1 STA_IND BAS I4 2 1 Station names indexes
DATA.1 STA_IND 3 0 1 1 1
DATA.1 STA_IND 3 0 2 1 7
STA_IND 属于 BAS 类,所以这里字段名之后的第一个整数仍然是基线观测序号 3。后面的元素索引分别选择第一站和第二站,最后的数值则是测站名称表中的序号。需要保留数据库给出的两站顺序,后续解释时延符号、构造基线向量或计算理论时延时,都要与相应程序的约定保持一致。
确定测站序号 1 和 7 后,还可以查询各站的坐标。测站坐标保存在 SIT_COOR 中:
TOCS.1 SIT_COOR SES R8 3 8 Site coordinates in a crust-fixed terrestrial reference system: X, Y, Z (meters)
数组大小为 (3,8),每一列对应一个测站,三个元素依次为地固参考系中的 $X$、$Y$、$Z$ 坐标,单位为 m。两端测站对应的数据为:
DATA.1 SIT_COOR 0 0 1 1 4.985370006000000D+06
DATA.1 SIT_COOR 0 0 2 1 -3.955020388000000D+06
DATA.1 SIT_COOR 0 0 3 1 -4.284720880000000D+05
DATA.1 SIT_COOR 0 0 1 7 4.075627620000000D+06
DATA.1 SIT_COOR 0 0 2 7 9.317743110000000D+05
DATA.1 SIT_COOR 0 0 3 7 4.801552379000000D+06
前三行是 FORTLEZA 的坐标,后三行是 WETTZ13N 的坐标。这些是数据库保存的测站坐标;仅凭这个字段,不能确定其具体参考框架实现和坐标历元。
气温、气压等随观测变化的数据,则保存在 STA 类字段中。本例对应:
TOCS.3 AIR_TEMP STA R8 1 1 Air temperature at the station (K)
TOCS.3 ATM_PRES STA R8 1 1 Atmospheric pressure at the station (Pa)
DATA.3 AIR_TEMP 1 1 1 1 3.035866668051416D+02
DATA.3 AIR_TEMP 1 7 1 1 2.751600000000000D+02
DATA.3 ATM_PRES 1 1 1 1 1.006700000000000D+05
DATA.3 ATM_PRES 1 7 1 1 9.428000000000000D+04
两个字段均属于 STA 类,以 8 字节实数保存,每个测站在其参与的每个观测段中各对应一个数值。AIR_TEMP 的单位为 K,ATM_PRES 的单位为 Pa。以大气压字段的第二条为例,这条记录可以逐项解释为:
| 内容 | 含义 |
|---|---|
DATA.3 |
第三数据块中的数据记录 |
ATM_PRES |
气压字段 |
前面的 1 |
该站参与的第 1 个观测段 |
接着的 7 |
测站序号,对应 WETTZ13N |
后面的 1 1 |
标量的两个元素索引 |
9.428000000000000D+04 |
气压值,约为 94280 Pa |
与这条基线相关的完整气象数据信息为:
| 测站 | 气温 / K | 气压 / Pa |
|---|---|---|
| FORTLEZA | 303.587 | 100670 |
| WETTZ13N | 275.160 | 94280 |
气象数据虽然与群时延位于不同的数据块,仍然通过观测和测站索引建立联系。
此外,STA 类的前两个索引需要单独注意,它们分别对应于测站序号和站内观测段序号。例如:
DATA.3 AIR_TEMP 1 7 1 1 2.751600000000000D+02
字段名之后的第一个整数 1,表示该测站自身参与的第 1 个观测段;第二个整数 7,表示测站名称表中的第 7 个站,即 WETTZ13N。如果某个测站缺席了前面的观测段,本站序号还可能与全局观测段序号不同。因此,读取 STA 类数据时,需要同时确定全局测站序号和该站自身的观测段序号。
如前所述,通过这些索引,一条基线观测记录可以关联到相应观测段的时间和目标源,也可以关联到两端测站的气象、电缆校准等数据。
三、读取和转换程序
3.1 VDA数据读取
实际解析 VDA 文件的函数是 GVH_READ_AGV,对应的源代码为gvh/gvh/gvh_read_agv.f。它先检查格式标识,读取字段定义和基础索引,再将其余数据放入内存中的数据结构。后续程序通过 GVH_GLCODE 按字段名称读取需要的数据。根据GVH_READ_AGV的读取逻辑,程序通过 OBS_TAB 找出该站参与的观测段,再按本站的顺序编号。前文的例子中,两站自身的第 1 个观测段都是全局第 1 个观测段,所以气象记录的首索引均为 1,不能使用基线观测序号 3。
从用户执行的命令到内部读写子程序,对应关系如下:
| 用途 | 用户执行的命令 | 内部负责读写的子程序 |
|---|---|---|
| 将 VDA 导入为二进制 GVF | gvf_import.py,内部调用 gvf_transform -to_binary |
GVH_READ_AGV 读取 VDA,GVH_WRITE_BGV 写出二进制文件 |
| 将二进制 GVF 导出为 VDA | gvf_transform -to_ascii |
GVH_READ_BGV 读取二进制文件,GVH_WRITE_AGV 写出 VDA |
在已经配置好数据库目录的 pSolve 环境中,可以使用以下命令导入 VDA:
gvf_import.py /data/vda/20230930_j.vda.bz2 INT
其中,INT 是数据库目录配置中的存储库名称。(这个在哪里定义?) gvf_import.py 负责必要的解压和参数处理,再调用 gvf_transform -to_binary 写出二进制 GVF 文件及相应的 .env 文件。
反过来,可以将二进制数据库导出为 VDA:
gvf_transform -to_ascii 20230930_j_v002 /tmp/20230930_j.vda INT
上面命令中的 INT 是在 vcat.conf 中定义的存储库名称。pSolve 提供的配置模板psolve/share/vcat_conf.tmpl 包含 OBS 和 INT 两个存储库,通常分别用于常规观测和 UT1 加强观测。以 /data/gvf 为数据根目录,相关配置项可以写成:
GVF_REP_NAMES: OBS INT
GVF_ENV_DIR: OBS /data/gvf/obs/env
GVF_DB_DIR: OBS /data/gvf/obs/db
GVF_ENV_DIR: INT /data/gvf/int/env
GVF_DB_DIR: INT /data/gvf/int/db
这只是配置文件中与存储库有关的部分。其中:
GVF_REP_NAMES登记可用的存储库名称;GVF_ENV_DIR指定.env文件的存放目录;GVF_DB_DIR指定二进制.bgv文件的存放目录。
命令末尾的 INT 明确选择了这个存储库,其用途和存放位置由配置文件决定。配置文件的路径由环境变量 VCAT_CONF 给出,查看其定义可以运行:
printenv VCAT_CONF
也可以通过下面的命令查询 pSolve 的 save 目录,再查看其中的 vcat.conf:
solve_inq --save
具体的查找过程与构建方式有关:独立构建的 gvf_transform 在未设置 VCAT_CONF 时,会先检查 PSOLVE_SAVE_DIR,再通过 solve_inq --save 查询;与 pSolve 一同构建的版本使用编译时指定的 SOLVE_SAVE_DIR。
3.2 从 vgosDB 转换为 VDA
vgosDB 数据可以通过 nuSolve 提供的 vgosDxConvertor 转换为 VDA。它是 nuSolve 软件包自带的脚本,db2da 模式表示从 vgosDB 转为 VDA:
vgosDxConvertor db2da input_vgosDB_file output_VDA_file
输入可以是 vgosDB 的数据库描述文件、包含数据库的目录,也可以是 .tgz 或 .tar.gz 归档文件。反向转换使用 da2db 模式。
例如,假设输入归档保存在以下路径,可以先转换为 VDA,再导入 pSolve:
vgosDxConvertor db2da /data/vgosdb/20230930_j.tgz /data/vda/20230930_j.vda
gvf_import.py /data/vda/20230930_j.vda INT
vgosDxConvertor 内部调用 nuSolve,并执行配套的 vgosDx.js。在 db2da 模式下,脚本先调用 importSession() 读取 vgosDB,再调用 saveDataAsVgosDa() 写出 VDA。SGDASS 中的 gvh/scripts/vdb_to_vda.py 也调用了 vgosDxConvertor db2da。实际读取 vgosDB 和写出 VDA 的工作由 nuSolve 完成。
对于 nuSolve 生成的 VDA,gvf_transform 在导入时会调用 GVH_FROM_NUSOLVE(源代码见/gvh/gvh_from_nusolve.f) 进行适配,包括调整部分字段、重新组织数据分段,并舍弃一些 nuSolve 专有字段。这些处理在转换程序说明中有所记录。通过这条转换流程,可以将 vgosDB 中的观测数据导入 pSolve,并按 pSolve 的数据组织方式保存为 GVF 数据库。