Skip to content

Using the Dead Reckoning libraryΒΆ

The aircraft example uses both DeadReckonerBase and SettableDeadReckoner<T>, the first to extrapolate the entity's position from the speed it was given, the second to write the result into the RPR Spatial attribute. This is the whole of it:

examples/packages/aircrafts/src/dummy_aircraft_impl.cpp
  // type alias
  using SettableDr = sen::util::SettableDeadReckoner<rpr::BaseEntityBase<>>;
  using DeadReckonerBase = sen::util::DeadReckonerBase;

public:
  using DummyAircraftBase<>::DummyAircraftBase;
  ~DummyAircraftImpl() override = default;

public:
  void registered(sen::kernel::RegistrationApi& api) override
  {
    std::ignore = api;

    // used to update the spatial field of the aircraft
    settableDeadReckoner_ =
      std::make_unique<SettableDr>(*this, sen::util::DrThreshold {0.0, 0.0f, sen::util::ReferenceSystem::world});

    // commanded speed can be modified by the user
    speedGuard_ = onSpeedChanged({this,
                                  [this]()
                                  {
                                    auto situation = deadReckoner_.geodeticSituation(api_->getTime());
                                    const auto& [north, east, down] = getSpeed();
                                    situation.velocityVector = {north, east, down};
                                    deadReckoner_.updateGeodeticSituation(std::move(situation));
                                  }});
  }

  void update(sen::kernel::RunApi& runApi) override
  {
    api_ = &runApi;

    if (const auto currentTime = runApi.getTime(); currentTime == runApi.getStartTime())
    {
      sen::util::GeodeticSituation initialSituation;
      initialSituation.timeStamp = currentTime;
      initialSituation.worldLocation = {40.0, 0.0, 10000.0};
      const auto& [north, east, down] = getSpeed();
      initialSituation.velocityVector = {north, east, down};
      deadReckoner_.updateGeodeticSituation(std::move(initialSituation));
    }
    // move the entity using the dead reckoner with the specified speed
    auto situation = deadReckoner_.geodeticSituation(runApi.getTime());

    // update the spatial using the settable dead reckoner
    settableDeadReckoner_->setSpatial(situation);
  }

SettableDeadReckoner<T> is constructed with *this, so it writes into the object's own spatial properties, and with a DrThreshold saying how far the extrapolation may drift from the data before the Spatial is written. The example passes zeros there, so it writes on every cycle.

DeadReckonerBase is not bound to an object: you give it a situation and ask for one back. That is why the example seeds it on the first cycle with a world location and the commanded velocity, and feeds it again whenever speed changes. Extrapolating an entity you own is not what these classes are for, since they exist to fill the gaps between updates from somewhere else, but it makes for a compact example.

A second, larger example lives in the Sen Sim Tools repository, which is a separate project and not part of this distribution. It adds a Dead Reckoning Viewer package that finds FOM objects on a bus, extrapolates their position and orientation, and plots the smoothed result against the raw values. If you have that repository checked out:

Run Dead Reckoning Viewer
python3 <sen_sim_tools_root>/examples/sen_dr/run_sen_dr.py

This example opens a Sen explorer window where you can plot the smoothed position of the entity and compare it with the position received from the Sen bus. The data from the bus comes from a recording where the entity was randomly moved to generate noisy spatial data. The input data from the recording can be seen in the dr.input bus and the smoothed output in the local.output bus.

The configuration of the DeadReckoner<T> class has some parameters that you may need to tune in some scenarios.

Consider the following recommendations:

  • If the smoothed solution becomes unstable (check the orientation, where this issue is more likely), try setting a smaller smoothingInterval, as it will prevent the acceleration predictor from overshooting.

  • If you see a lack of responsiveness in the position/orientation, try decreasing the corresponding convergence time. Keep in mind that a smaller convergence time can lead to instabilities in the smoothing algorithm.

  • If the smoothed solution is underdamped and oscillates, try increasing the corresponding damping coefficient until you see these oscillations disappear.

  • Keep in mind that the Dead Reckoner works well when the updates come in at a considerable frequency (at least 20 Hz). If data is received at a slower rate, the smoothed solution could slightly oscillate around the incoming updates. This is most noticeable when extrapolating orientations from updates that carry no angular velocity.