传播

接下来的4个教程详细介绍了传播包的一些基本用法,这些用法在库架构文档的传播部分中有描述。

步骤管理

根据调用应用程序的需求,传播器可以在初始时间和传播目标时间之间提供中间状态,或者仅提供最终状态。

仅最终状态

当用户想要完全控制时间演化时,使用此方法。应用程序给出目标时间,但没有任何步骤处理程序。

让我们将EME2000惯性参考系定义为参考坐标系:

Frame inertialFrame = FramesFactory.getEME2000();

让我们设置一个初始状态:

TimeScale utc = TimeScalesFactory.getUTC();
AbsoluteDate initialDate = new AbsoluteDate(2004, 01, 01, 23, 30, 00.000, utc);

double mu =  3.986004415e+14;

double a = 24396159;                     // 半长轴,单位为米
double e = 0.72831215;                   // 偏心率
double i = FastMath.toRadians(7);        // 倾角
double omega = FastMath.toRadians(180);  // 近地点幅角
double raan = FastMath.toRadians(261);   // 升交点赤经
double lM = 0;                           // 平近点角

初始轨道定义为KeplerianOrbit。有关轨道表示的更多详细信息可以在库架构文档的轨道部分中找到。

Orbit initialOrbit = new KeplerianOrbit(a, e, i, omega, raan, lM, PositionAngleType.MEAN,
                                        inertialFrame, initialDate, mu);

我们选择使用一个非常简单的KeplerianPropagator来计算基本的开普勒运动。它可以是任何可用的传播器。

KeplerianPropagator kepler = new KeplerianPropagator(initialOrbit);

最后,定义了持续时间和步长的传播特性,并进行传播循环以便在每个步骤打印结果:

double duration = 600.;
AbsoluteDate finalDate = initialDate.shiftedBy(duration);
double stepT = 60.;
int cpt = 1;
for (AbsoluteDate extrapDate = initialDate;
     extrapDate.compareTo(finalDate) <= 0;
     extrapDate = extrapDate.shiftedBy(stepT))  {
    SpacecraftState currentState = kepler.propagate(extrapDate);
    System.out.format(Locale.US, "step %2d %s %s%n",
                      cpt++, currentState.getDate(), currentState.getOrbit());
}

打印的结果如下所示:

步骤  1 2004-01-01T23:30:00.000 开普勒参数: {a: 2.4396159E7; e: 0.72831215; i: 7.0; pa: 180.0; raan: 261.0; v: 0.0;}
步骤  2 2004-01-01T23:31:00.000 开普勒参数: {a: 2.4396159E7; e: 0.72831215; i: 7.0; pa: 180.0; raan: 261.0; v: 5.281383633573694;}
步骤  3 2004-01-01T23:32:00.000 开普勒参数: {a: 2.4396159E7; e: 0.72831215; i: 7.0; pa: 180.0; raan: 261.0; v: 10.525261309411585;}
步骤  4 2004-01-01T23:33:00.000 开普勒参数: {a: 2.4396159E7; e: 0.72831215; i: 7.0; pa: 180.0; raan: 261.0; v: 15.695876839823306;}
步骤  5 2004-01-01T23:34:00.000 开普勒参数: {a: 2.4396159E7; e: 0.72831215; i: 7.0; pa: 180.0; raan: 261.0; v: 20.76077035517381;}
步骤  6 2004-01-01T23:35:00.000 开普勒参数: {a: 2.4396159E7; e: 0.72831215; i: 7.0; pa: 180.0; raan: 261.0; v: 25.691961427063767;}
步骤  7 2004-01-01T23:36:00.000 开普勒参数: {a: 2.4396159E7; e: 0.72831215; i: 7.0; pa: 180.0; raan: 261.0; v: 30.466680460539763;}
步骤  8 2004-01-01T23:37:00.000 开普勒参数: {a: 2.4396159E7; e: 0.72831215; i: 7.0; pa: 180.0; raan: 261.0; v: 35.06763907945756;}
步骤  9 2004-01-01T23:38:00.000 开普勒参数: {a: 2.4396159E7; e: 0.72831215; i: 7.0; pa: 180.0; raan: 261.0; v: 39.48289615024968;}
步骤 10 2004-01-01T23:39:00.000 开普勒参数: {a: 2.4396159E7; e: 0.72831215; i: 7.0; pa: 180.0; raan: 261.0; v: 43.70541689946282;}
步骤 11 2004-01-01T23:40:00.000 开普勒参数: {a: 2.4396159E7; e: 0.72831215; i: 7.0; pa: 180.0; raan: 261.0; v: 47.732436294590705;}

此示例的完整代码可以在教程的源代码树中找到,文件名为src/main/java/org/orekit/tutorials/propagation/KeplerianPropagation.java

中间状态

为了获取中间状态,用户需要注册一些自定义对象实现OrekitStepHandlerOrekitFixedStephHandler

如上面的“仅最终状态”示例中所示,让我们定义一些初始状态:

// 惯性参考系
Frame inertialFrame = FramesFactory.getEME2000();
// 初始日期
TimeScale utc = TimeScalesFactory.getUTC();
AbsoluteDate initialDate = new AbsoluteDate(2004, 01, 01, 23, 30, 00.000,utc);
// 中心引力系数
double mu =  3.986004415e+14;
// 初始轨道
double a = 24396159;                    // 半长轴,单位为米
double e = 0.72831215;                  // 偏心率
double i = FastMath.toRadians(7);       // 倾角
double omega = FastMath.toRadians(180); // 近地点幅角
double raan = FastMath.toRadians(261);  // 升交点赤经
double lM = 0;                          // 平近点角
Orbit initialOrbit = new KeplerianOrbit(a, e, i, omega, raan, lM, PositionAngleType.MEAN,
                                        inertialFrame, initialDate, mu);
// 初始状态定义
SpacecraftState initialState = new SpacecraftState(initialOrbit);

这里我们使用了一个更复杂的NumericalPropagator,它基于底层的Hipparchus库提供的自适应步长积分器,但使用哪个积分器并不重要。

// 自适应步长积分器
// 最小步长为0.001,最大步长为1000
double minStep = 0.001;
double maxstep = 1000.0;
double positionTolerance = 10.0;
OrbitType propagationType = OrbitType.KEPLERIAN;
double[][] tolerances =
    NumericalPropagator.tolerances(positionTolerance, initialOrbit, propagationType);
AdaptiveStepsizeIntegrator integrator =
    new DormandPrince853Integrator(minStep, maxstep, tolerances[0], tolerances[1]);

我们设置了积分器,并强制其使用开普勒参数进行传播。

NumericalPropagator propagator = new NumericalPropagator(integrator);
propagator.setOrbitType(propagationType);

一个力模型,这里简化为单个扰动引力场,被考虑在内。有关力模型的更多细节可以在库架构文档的力模型部分找到。

NormalizedSphericalHarmonicsProvider provider =
    GravityFieldFactory.getNormalizedProvider(10, 10);
ForceModel holmesFeatherstone =
    new HolmesFeatherstoneAttractionModel(FramesFactory.getITRF(IERSConventions.IERS_2010,
                                                                true),
                                          provider)

这个力模型简单地添加到了传播器中:

propagator.addForceModel(holmesFeatherstone);

然后,为传播器设置初始状态:

propagator.setInitialState(initialState);

传播器的配置通过一个固定步长的TutorialStepHandler完成,该类实现了OrekitFixedStepHandler接口,以便在循环中调用handleStep方法。对于本教程,handleStep方法只会在每个步长时打印当前状态。

propagator.getMultiplexer().add(60., new TutorialStepHandler());

最后,传播器只需从初始状态开始,传播一定的时间。

SpacecraftState finalState = propagator.propagate(new AbsoluteDate(initialDate, 630.));

显然,通过几行代码,主应用程序将定期输出的处理委托给传播器,通过变步长积分循环。

只需要从OrekitFixedStepHandler接口派生出一些类,实现handleStep方法,如下所示:

private static class TutorialStepHandler implements OrekitFixedStepHandler {

    public void init(final SpacecraftState s0, final AbsoluteDate t, final double step) {
        System.out.println("          日期                a           e" +
                           "           i         \u03c9          \u03a9" +
                           "          \u03bd");
    }

    public void handleStep(SpacecraftState currentState) {
        KeplerianOrbit o = (KeplerianOrbit) OrbitType.KEPLERIAN.convertType(currentState.getOrbit());
        System.out.format(Locale.US, "%s %12.3f %10.8f %10.6f %10.6f %10.6f %10.6f%n",
                          currentState.getDate(),
                          o.getA(), o.getE(),
                          FastMath.toDegrees(o.getI()),
                          FastMath.toDegrees(o.getPerigeeArgument()),
                          FastMath.toDegrees(o.getRightAscensionOfAscendingNode()),
                          FastMath.toDegrees(o.getTrueAnomaly()));
    }

    public void finish(final SpacecraftState finalState) {
        System.out.println("这是最后一步");
        System.out.println();
    }

}

打印的结果如下所示:

          日期                a           e           i         ω          Ω          ν
2004-01-01T23:30:00.000Z 24396159.000 0.72831215   7.000000 180.000000 261.000000   0.000000
2004-01-01T23:31:00.000Z 24395672.948 0.72830573   6.999927 180.010992 260.999966   5.270439
2004-01-01T23:32:00.000Z 24394149.110 0.72828580   6.999758 180.021885 260.999767  10.503710
2004-01-01T23:33:00.000Z 24391676.555 0.72825352   6.999506 180.032557 260.999281  15.664370
2004-01-01T23:34:00.000Z 24388396.501 0.72821073   6.999190 180.042889 260.998422  20.720220
2004-01-01T23:35:00.000Z 24384487.471 0.72815976   6.998827 180.052785 260.997139  25.643463
2004-01-01T23:36:00.000Z 24380142.510 0.72810308   6.998434 180.062182 260.995416  30.411433
2004-01-01T23:37:00.000Z 24375546.779 0.72804310   6.998029 180.071043 260.993268  35.006866
2004-01-01T23:38:00.000Z 24370861.668 0.72798191   6.997624 180.079354 260.990738  39.417786
2004-01-01T23:39:00.000Z 24366216.959 0.72792119   6.997234 180.087112 260.987893  43.637076
2004-01-01T23:40:00.000Z 24361709.735 0.72786221   6.996868 180.094320 260.984809  47.661865
这是最后一步

最终状态:
2004-01-01T23:40:30.000 24359529.816 0.72783366   6.996697 180.097720 260.983203  49.601407

这个示例的完整代码可以在库的源代码树中找到,文件位于 src/main/java/org/orekit/tutorials/propagation/NumericalPropagation.java

星历生成

当用户需要在初始日期和结束日期之间的任意时间访问轨道状态时,可以使用第三种模式。

与上面的两个示例一样,首先定义一些初始状态:

// 惯性坐标系
Frame inertialFrame = FramesFactory.getEME2000();
// 初始日期
TimeScale utc = TimeScalesFactory.getUTC();
AbsoluteDate initialDate = new AbsoluteDate(2004, 01, 01, 23, 30, 00.000, utc);
// 中心引力系数
double mu =  3.986004415e+14;
// 初始轨道
double a = 24396159;                    // 半长轴,单位为米
double e = 0.72831215;                  // 偏心率
double i = FastMath.toRadians(7);       // 倾角
double omega = FastMath.toRadians(180); // 近地点幅角
double raan = FastMath.toRadians(261);  // 升交点赤经
double lM = 0;                          // 平近点角
Orbit initialOrbit = new KeplerianOrbit(a, e, i, omega, raan, lM, PositionAngleType.MEAN,
                                        inertialFrame, initialDate, mu);
// 初始状态定义
SpacecraftState initialState = new SpacecraftState(initialOrbit);

这里使用了一个简单的NumericalPropagator,没有考虑扰动,基于底层的Hipparchus库提供的经典固定步长龙格-库塔积分器。

double stepSize = 10;
AbstractIntegrator integrator = new ClassicalRungeKuttaIntegrator(stepSize);
NumericalPropagator propagator = new NumericalPropagator(integrator);

为该传播器设置初始状态:

propagator.setInitialState(initialState);

然后使用传播器的星历生成器:

final EphemerisGenerator generator = propagator.getEphemerisGenerator();

并对给定的持续时间进行传播。

SpacecraftState finalState = propagator.propagate(initialDate.shiftedBy(6000));

这个finalState可以用于任何用途,例如打印如下所示:

 数值传播:
  最终日期:2004-01-02T01:10:00.000Z
  等时参数:{a: 2.4396159E7;
             ex: 0.11393312156755062; ey: 0.719345418868777;
             hx: -0.009567941763699867; hy: -0.06040960680288257;
             lv: 583.1250344407331;}

在传播过程中,中间状态存储在一个星历中,可以通过单个调用来恢复:

BoundedPropagator ephemeris = generator.getGeneratedEphemeris();
System.out.println(" 从 " + ephemeris.getMinDate() + " 到 " + ephemeris.getMaxDate() + " 定义的星历");

星历被定义为BoundedPropagator,这意味着它只在传播的初始日期和最终日期之间有效。上面的代码给出了以下结果:

星历定义从2004-01-01T23:30:00.000Z到2004-01-02T01:10:00.000

在这些日期之间,星历可以像任何传播器一样使用,通过一次调用将轨道状态传播到任何中间日期:

SpacecraftState intermediateState = ephemeris.propagate(intermediateDate);

下面是将中间日期设置为开始日期之后3000秒和正好是最终日期的结果:

星历传播:
日期:2004-01-02T00:20:00.000Z
赤道参数:{a: 2.4396159E7;
                          ex: 0.11393312156755062; ey: 0.719345418868777;
                          hx: -0.009567941763699867; hy: -0.06040960680288257;
                          lv: 559.0092657655282;}

日期:2004-01-02T01:10:00.000Z
赤道参数:{a: 2.4396159E7;
                          ex: 0.11393312156755062; ey: 0.719345418868777;
                          hx: -0.009567941763699867; hy: -0.06040960680288257;
                          lv: 583.1250344407331;}

下面显示了当我们尝试使用超出星历范围的日期时(在这种情况下,中间日期设置为星历开始前1000秒),我们得到的错误消息:

星历范围外的日期:2004-01-01T23:13:20.000Z在[2004-01-01T23:30:00.000Z, 2004-01-02T01:10:00.000Z]之前1.0E3秒

此示例的完整代码可以在库的源代码树中的文件src/main/java/org/orekit/tutorials/propagation/EphemerisMode.java中找到。

事件管理

本教程旨在演示事件处理机制的强大和简单性。

我们需要在某个时间范围内检查卫星和地面站之间的可见性。

我们将使用并扩展预定义的ElevationDetector来执行任务。

首先,让我们为卫星设置一个初始状态,该状态由某个惯性参考系中某个日期的位置和速度定义。

Vector3D position  = new Vector3D(-6142438.668, 3492467.560, -25767.25680);
Vector3D velocity  = new Vector3D(505.8479685, 942.7809215, 7435.922231);
PVCoordinates pvCoordinates = new PVCoordinates(position, velocity);
AbsoluteDate initialDate =
    new AbsoluteDate(2004, 01, 01, 23, 30, 00.000, TimeScalesFactory.getUTC());
Frame inertialFrame = FramesFactory.getEME2000();

我们还需要设置中心引力系数,以将初始轨道定义为KeplerianOrbit

double mu =  3.986004415e+14;
Orbit initialOrbit =
    new KeplerianOrbit(pvCoordinates, inertialFrame, initialDate, mu);

有关轨道表示的更多细节可以在库架构文档的轨道部分找到。

作为传播器,我们考虑使用KeplerianPropagator来计算简单的开普勒运动。它可以更复杂,而不改变本教程的通用目的。

Propagator kepler = new KeplerianPropagator(initialOrbit);

然后,让我们通过其坐标将地面站定义为GeodeticPoint

double longitude = FastMath.toRadians(45.);
double latitude  = FastMath.toRadians(25.);
double altitude  = 0.;
GeodeticPoint station1 = new GeodeticPoint(latitude, longitude, altitude);

并将其与与某个地球参考系中的BodyShape相关联的TopocentricFrame关联起来。

Frame earthFrame = FramesFactory.getITRF(IERSConventions.IERS_2010, true);
BodyShape earth = new OneAxisEllipsoid(Constants.WGS84_EARTH_EQUATORIAL_RADIUS,
                                       Constants.WGS84_EARTH_FLATTENING,
                                       earthFrame);
TopocentricFrame sta1Frame = new TopocentricFrame(earth, station1, "station1");

有关BodyShapeGeodeticPoint的更多细节可以在库架构文档的天体部分找到。
有关TopocentricFrame的更多细节可以在库架构文档的参考系部分找到。

EventDetector被定义为具有恒定高程限制和专用处理程序的ElevationDetector,在这种简单情况下,可以将其内联编写为实现EventHandler接口的eventOccurred方法的lambda函数:

double maxcheck  = 60.0;
double threshold =  0.001;
double elevation = FastMath.toRadians(5.);
EventDetector sta1Visi =
  new ElevationDetector(maxcheck, threshold, sta1Frame).
  withConstantElevation(elevation).
  withHandler((s, detector, increasing) -> {
                    System.out.println(" 在 " +
                                       detector.getTopocentricFrame().getName() +
                                       (increasing ? " 开始可见性 " : " 结束可见性 ") +
                                       s.getDate().toStringWithoutUtcOffset(utc, 3));
                    return increasing ? Action.CONTINUE : Action.STOP;
                });

将此EventDetector添加到传播器中:

kepler.addEventDetector(sta1Visi);

传播器将从初始日期传播到第一次升起或按照VisibilityHandler类中实现的行为进行固定持续时间的传播。

SpacecraftState finalState =
    kepler.propagate(new AbsoluteDate(initialDate, 1500.));
System.out.println(" 最终状态 : " + finalState.getDate().durationFrom(initialDate));

打印的结果如下所示。我们可以看到传播在检测到设置后停止:

在 station1 上开始可见性 2004-01-01T23:31:52.098
在 station1 上结束可见性 2004-01-01T23:42:48.850
从初始日期到最终状态的持续时间(秒):768.850267132939

此示例的完整代码可以在教程的源代码树中的文件src/main/java/org/orekit/tutorials/propagation/VisibilityCheck.java中找到。