数据上下文

Orekit库依赖于一些外部数据来进行物理模型计算。典型的数据包括地球定向参数和闰秒历史,这些数据由IERS提供,或者由JPL或IMCCE提供的行星星历。这些数据可以以特定格式的文本或二进制文件存储,Orekit知道如何找到并读取这些文件,也可以存储在用户应用程序可以连接的数据库中。这些数据也可以完全嵌入在构建在Orekit之上的应用程序中。

一组一致的数据被称为数据上下文。

常规的独立应用程序只会使用一个数据上下文,即一个EOP集合,一个JPL星历集合,一个闰秒历史...但是对于某些需求来说,只有一个数据上下文是不够的,例如:

Orekit必须进行适当的配置,以为每个任务选择正确的上下文。

上下文化数据与固定数据

只有几十千字节且可以假定永远不会更改的数据,例如预定义的岁差-章动模型的常数,已经嵌入在核心库中,它们不与任何数据上下文相关联。通过在代码本身中设置常量来定义小而简单的数据集。例如,地球时间和国际原子时间之间的32.184秒偏移量就是这种情况。大型或复杂的固定数据通过将相应的资源文件原样复制到编译后的jar文件中的assets目录下来嵌入。例如,这是IAU-2000岁差-章动模型表的情况。对于这些固定数据集,无需进行任何配置,因为它们已经嵌入在库中,所以用户可以完全忽略它们。

其他类型的数据集对应于庞大或变化的数据集。从长远来看,这些数据集无法实际上嵌入到特定版本的库中。这样的数据集的典型示例是地球定向参数,这些参数对于精确的坐标转换是必需的。另一个例子是行星星历。IERS定期发布新的地球定向参数文件,涵盖新的时间范围,而行星星历则代表几兆字节大小的文件。这些数据集必须由用户通过数据上下文提供,并且Orekit必须在运行时配置以找到并加载它们。

Orekit中的上下文处理

下面的类图展示了Orekit中如何处理上下文。

数据类图

顶层接口是DataContext。它收集了Orekit用于创建物理模型所需的所有工厂的链接。有一个用于TimeScales的工厂,它需要UTC-TAI历史数据来创建UTC时间尺度,以及需要EOP数据来创建UT1时间尺度。有一个用于坐标系的工厂,它需要地球坐标系的EOP数据。有一个用于天体的工厂,它需要所有天体的行星星历数据。

DataContext接口有一个实现LazyLoadedDataContext,它在首次访问时从外部资源(通常是文件)上动态加载数据,并依赖于DataProvidersManager来查找如何定位和解析这些资源。

用户可以提供自己的DataContext实现,例如如果所有数据存储在特定任务的数据库中,这将不适用于基于资源或文件的LazyLoadedDataContextDataProvidersManager

提供DataContext的实现包括创建自定义工厂并设置一个类以获得所有工厂的单一访问点。预定义的LazyLoadedDataContext例如使用LazyLoadedTimeScalesLazyLoadedFramesLazyLoadedEOPLazyLoadedCelestialBodies,它们都依赖于相同的DataProvidersManager来查找和加载数据。在这个实现中,一些工厂依赖于其他工厂,因为一个数据上下文必须提供一致的数据。例如,EOP数据既被LazyLoadedFrames(用于地球坐标系)使用,也被LazyLoadedTimeScales(用于UT1)使用。而LazyLoadedFrames还依赖于CelestialBodies接口(用于ICRF)。

各个工厂中有一些静态方法(例如TimeScales.of(...)Frames.of(...)),它们创建带有预加载常量数据的工厂。这些方法对于需要实现自己的数据上下文的用户非常有用。

使用上下文

需要使用特定数据上下文的应用程序必须指定它。如果使用的是直接引用依赖于上下文的物理模型(例如ITRF地球坐标系),则可以从与所需上下文关联的Frames工厂中获取模型的引用:

Frame itrf = myDataContext.getFrames().getITRF();

有时,引用是隐式的,并且在Orekit代码中隐藏。在这种情况下,将使用默认上下文。例如,仅使用GalileoOrbitalElements创建Galileo专用的传播器的构建器将自动从默认上下文中检索EME2000和ITRF坐标系。如果默认上下文不是用户想要使用的上下文,则必须通过显式地给出Frames工厂来创建构建器。因此,有许多构造函数和方法有两个签名,一个没有DataContext或工厂,另一个带有DataContext或工厂,用户必须根据自己的需求选择正确的构造函数/方法进行调用。

这个特性对于需要多个上下文的应用程序非常有用。

避免使用默认数据上下文

当应用程序需要使用自定义数据上下文并避免使用默认数据上下文时,有几个工具可以帮助找到使用默认数据上下文的位置。首先是一个编译器插件,它可以检测应用程序是否使用了被注解为@DefaultDataContext的方法/构造函数/字段/类,而调用它的方法/构造函数/字段/类没有该注解。Orekit中所有使用默认数据上下文的方法、构造函数和字段都有这个注解。这个注解和插件的操作方式类似于@Deprecated注解。它将找到所有在编译时已知的使用情况。要使用该插件,将-Xplugin:dataContextPlugin添加到javac命令行参数中。例如,下面是一个使用编译器插件的pom.xml片段:

<project>
  ...
  <dependencies>
    <dependency>
      <groupId>org.orekit</groupId>
      <artifactId>orekit</artifactId>
      <version>10.1</version>
      <scope>compile</scope>
    </dependency>
  </dependencies>
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <configuration>
          <showWarnings>true</showWarnings>
          <compilerArgs>
            <arg>-Xplugin:dataContextPlugin</arg>
          </compilerArgs>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

编译器插件可以检测到在编译时已知的使用默认数据上下文的情况,但无法检测到多态使用默认数据上下文的情况。例如,插件将检测到Object o = AbsoluteDate.J2000_EPOCH;使用了默认数据上下文,但不会检测到o.toString();。要检测后者类型的情况,需要使用运行时工具。

检测使用默认数据上下文的第二个工具是ExceptionalDataContext。这是一个简单地在调用其方法时抛出异常的DataContext。在运行测试时,可以使用它来确保被测试的代码不访问默认数据上下文。因为Orekit中的一些静态字段使用默认数据上下文进行初始化,所以在替换默认数据上下文之前必须先初始化这些类。要使用ExceptionalDataContext,请在使用Orekit的任何其他代码之前运行以下代码:

// 强制初始化使用默认数据上下文的类的静态字段
Object o = AbsoluteDate.ARBITRARY_DATE;
o = InertialProvider.of(Frame);
// 阻止进一步使用默认数据上下文
DataContext.setDefault(new ExceptionalDataContext());

ExceptionalDataContext无法检测使用默认数据上下文的静态字段的使用情况,例如AbsoluteDateInertialProvider中的字段。同时使用编译器插件和ExceptionalDataContext将能够检测到所有使用默认数据上下文的情况。这提供了一种验证调用重载的构造函数或方法的正确实例,以避免意外使用默认数据上下文的方式。