using System;
using MyParking.Shared;
namespace MultiWheelC.StateEstimation
{
///
/// 根据连续有效的Detour世界位姿和真实时间差估算车辆二维速度。
///
public sealed class VelocityEstimator2D
{
public const double DefaultLinearFilterTimeConstantSeconds =
0.15;
public const double DefaultAngularFilterTimeConstantSeconds =
0.20;
private readonly FirstOrderLowPassFilter
_worldVelocityXFilter;
private readonly FirstOrderLowPassFilter
_worldVelocityYFilter;
private readonly FirstOrderLowPassFilter
_angularVelocityFilter;
private bool _hasPreviousSample;
private Pose2D _previousPoseInWorld;
private double _previousTimestampSeconds;
///
/// 创建使用默认0.15s线速度和0.20s角速度时间常数的估计器。
///
public VelocityEstimator2D()
: this(
DefaultLinearFilterTimeConstantSeconds,
DefaultAngularFilterTimeConstantSeconds)
{
}
///
/// 创建使用指定线速度和角速度滤波时间常数的估计器。
///
public VelocityEstimator2D(
double linearFilterTimeConstantSeconds,
double angularFilterTimeConstantSeconds)
{
_worldVelocityXFilter =
new FirstOrderLowPassFilter(
linearFilterTimeConstantSeconds);
_worldVelocityYFilter =
new FirstOrderLowPassFilter(
linearFilterTimeConstantSeconds);
_angularVelocityFilter =
new FirstOrderLowPassFilter(
angularFilterTimeConstantSeconds);
}
///
/// 获取是否已经保存了可用于下一次差分的位姿基准。
///
public bool HasPreviousSample =>
_hasPreviousSample;
///
/// 使用一个新的有效定位样本更新并返回车辆状态。
///
public VehicleState Update(
Pose2D poseInWorld,
double sampleTimestampSeconds)
{
EnsureFinitePose(
poseInWorld,
nameof(poseInWorld));
EnsureFiniteNonNegative(
sampleTimestampSeconds,
nameof(sampleTimestampSeconds));
var normalizedPoseInWorld =
new Pose2D(
poseInWorld.XMeters,
poseInWorld.YMeters,
AngleMath.NormalizeRadians(
poseInWorld.YawRadians));
if (!_hasPreviousSample)
{
return Reset(
normalizedPoseInWorld,
sampleTimestampSeconds);
}
var deltaTimeSeconds =
sampleTimestampSeconds -
_previousTimestampSeconds;
if (deltaTimeSeconds <= 0.0)
{
throw new ArgumentOutOfRangeException(
nameof(sampleTimestampSeconds),
"新定位样本的单调时间戳必须严格大于上一帧。");
}
var rawVelocityXInWorld =
(normalizedPoseInWorld.XMeters -
_previousPoseInWorld.XMeters) /
deltaTimeSeconds;
var rawVelocityYInWorld =
(normalizedPoseInWorld.YMeters -
_previousPoseInWorld.YMeters) /
deltaTimeSeconds;
var rawAngularVelocity =
AngleMath.ShortestDifferenceRadians(
normalizedPoseInWorld.YawRadians,
_previousPoseInWorld.YawRadians) /
deltaTimeSeconds;
var filteredTwistInWorld =
new Twist2D(
_worldVelocityXFilter.Update(
rawVelocityXInWorld,
deltaTimeSeconds),
_worldVelocityYFilter.Update(
rawVelocityYInWorld,
deltaTimeSeconds),
_angularVelocityFilter.Update(
rawAngularVelocity,
deltaTimeSeconds));
_previousPoseInWorld =
normalizedPoseInWorld;
_previousTimestampSeconds =
sampleTimestampSeconds;
return new VehicleState(
sampleTimestampSeconds,
normalizedPoseInWorld,
filteredTwistInWorld,
true);
}
///
/// 更新位姿差分基准但保留当前滤波速度,避免定位跳变形成虚假速度尖峰。
///
public VehicleState RebasePreservingVelocity(
Pose2D poseInWorld,
double sampleTimestampSeconds)
{
EnsureFinitePose(
poseInWorld,
nameof(poseInWorld));
EnsureFiniteNonNegative(
sampleTimestampSeconds,
nameof(sampleTimestampSeconds));
var normalizedPoseInWorld =
new Pose2D(
poseInWorld.XMeters,
poseInWorld.YMeters,
AngleMath.NormalizeRadians(
poseInWorld.YawRadians));
_previousPoseInWorld =
normalizedPoseInWorld;
_previousTimestampSeconds =
sampleTimestampSeconds;
_hasPreviousSample = true;
var hasValidVelocityEstimate =
_worldVelocityXFilter.IsInitialized &&
_worldVelocityYFilter.IsInitialized &&
_angularVelocityFilter.IsInitialized;
var retainedTwistInWorld =
hasValidVelocityEstimate
? new Twist2D(
_worldVelocityXFilter.Value,
_worldVelocityYFilter.Value,
_angularVelocityFilter.Value)
: Twist2D.Zero;
return new VehicleState(
sampleTimestampSeconds,
normalizedPoseInWorld,
retainedTwistInWorld,
hasValidVelocityEstimate);
}
///
/// 使用当前定位重新建立差分基准,并返回速度无效的零速状态。
///
public VehicleState Reset(
Pose2D poseInWorld,
double sampleTimestampSeconds)
{
EnsureFinitePose(
poseInWorld,
nameof(poseInWorld));
EnsureFiniteNonNegative(
sampleTimestampSeconds,
nameof(sampleTimestampSeconds));
_previousPoseInWorld =
new Pose2D(
poseInWorld.XMeters,
poseInWorld.YMeters,
AngleMath.NormalizeRadians(
poseInWorld.YawRadians));
_previousTimestampSeconds =
sampleTimestampSeconds;
_hasPreviousSample = true;
_worldVelocityXFilter.Reset();
_worldVelocityYFilter.Reset();
_angularVelocityFilter.Reset();
return new VehicleState(
sampleTimestampSeconds,
_previousPoseInWorld,
Twist2D.Zero,
false);
}
///
/// 清除差分基准和全部滤波历史,使下一帧重新初始化估计器。
///
public void Reset()
{
_hasPreviousSample = false;
_previousPoseInWorld = Pose2D.Identity;
_previousTimestampSeconds = 0.0;
_worldVelocityXFilter.Reset();
_worldVelocityYFilter.Reset();
_angularVelocityFilter.Reset();
}
///
/// 检查位姿是否由有限数值组成。
///
private static void EnsureFinitePose(
Pose2D pose,
string parameterName)
{
if (!IsFinite(pose.XMeters) ||
!IsFinite(pose.YMeters) ||
!IsFinite(pose.YawRadians))
{
throw new ArgumentOutOfRangeException(
parameterName,
"速度估计使用的车辆位姿必须由有限数值组成。");
}
}
///
/// 检查数值是否为非负有限值。
///
private static void EnsureFiniteNonNegative(
double value,
string parameterName)
{
if (!IsFinite(value) || value < 0.0)
{
throw new ArgumentOutOfRangeException(
parameterName,
"速度估计使用的采样时刻必须是非负有限值。");
}
}
///
/// 判断数值是否可用于速度估计。
///
private static bool IsFinite(double value)
{
return !double.IsNaN(value) &&
!double.IsInfinity(value);
}
}
}