using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
namespace MultiWheelC.TrajectoryPlanning.EMPlanner;
/// Finite piecewise-linear speed limits indexed exclusively by actual lateral PathS.
public sealed class PathSpeedLimit
{
private const double StationTolerance = 1e-12d;
internal PathSpeedLimit(IReadOnlyList pathS, IReadOnlyList maximumSpeed,
IReadOnlyList lateralAccelerationLimit, IReadOnlyList curvatureRateLimit,
IReadOnlyList stoppingLimit, double directionMaximumSpeedMetersPerSecond, bool hasStopBoundary)
{
PathS = CopyStrictStations(pathS, nameof(pathS));
MaximumSpeedMetersPerSecond = CopyFiniteNonnegative(maximumSpeed, PathS.Count, nameof(maximumSpeed));
LateralAccelerationSpeedLimitsMetersPerSecond = CopyFiniteNonnegative(lateralAccelerationLimit, PathS.Count,
nameof(lateralAccelerationLimit));
CurvatureRateSpeedLimitsMetersPerSecond = CopyFiniteNonnegative(curvatureRateLimit, PathS.Count,
nameof(curvatureRateLimit));
StoppingSpeedLimitsMetersPerSecond = CopyFiniteNonnegative(stoppingLimit, PathS.Count, nameof(stoppingLimit));
if (!IsFinite(directionMaximumSpeedMetersPerSecond) || directionMaximumSpeedMetersPerSecond <= 0d)
throw new ArgumentOutOfRangeException(nameof(directionMaximumSpeedMetersPerSecond));
if (hasStopBoundary &&
(MaximumSpeedMetersPerSecond[MaximumSpeedMetersPerSecond.Count - 1] != 0d ||
StoppingSpeedLimitsMetersPerSecond[StoppingSpeedLimitsMetersPerSecond.Count - 1] != 0d))
{
throw new ArgumentException("A real stop boundary must have an exact zero speed limit.");
}
DirectionMaximumSpeedMetersPerSecond = directionMaximumSpeedMetersPerSecond;
HasStopBoundary = hasStopBoundary;
}
public IReadOnlyList PathS { get; }
public IReadOnlyList MaximumSpeedMetersPerSecond { get; }
public IReadOnlyList LateralAccelerationSpeedLimitsMetersPerSecond { get; }
public IReadOnlyList CurvatureRateSpeedLimitsMetersPerSecond { get; }
public IReadOnlyList StoppingSpeedLimitsMetersPerSecond { get; }
public double DirectionMaximumSpeedMetersPerSecond { get; }
public bool HasStopBoundary { get; }
public double PathUpperBoundS { get { return PathS[PathS.Count - 1]; } }
public double TerminalPathS { get { return PathUpperBoundS; } }
public double MaximumSpeedAt(double pathS)
{
return Interpolate(MaximumSpeedMetersPerSecond, pathS);
}
public double LateralAccelerationLimitAt(double pathS)
{
return Interpolate(LateralAccelerationSpeedLimitsMetersPerSecond, pathS);
}
public double CurvatureRateLimitAt(double pathS)
{
return Interpolate(CurvatureRateSpeedLimitsMetersPerSecond, pathS);
}
public double StoppingLimitAt(double pathS)
{
return Interpolate(StoppingSpeedLimitsMetersPerSecond, pathS);
}
private double Interpolate(IReadOnlyList values, double pathS)
{
if (!IsFinite(pathS) || pathS < PathS[0] - StationTolerance || pathS > PathUpperBoundS + StationTolerance)
throw new ArgumentOutOfRangeException(nameof(pathS));
if (pathS <= PathS[0])
return values[0];
if (pathS >= PathUpperBoundS)
return values[values.Count - 1];
for (int index = 1; index < PathS.Count; index++)
{
if (pathS <= PathS[index])
{
double fraction = (pathS - PathS[index - 1]) / (PathS[index] - PathS[index - 1]);
return values[index - 1] + (values[index] - values[index - 1]) * fraction;
}
}
return values[values.Count - 1];
}
private static IReadOnlyList CopyStrictStations(IReadOnlyList source, string parameterName)
{
if (source == null || source.Count < 2)
throw new ArgumentException("At least two PathS stations are required.", parameterName);
var copy = new List(source.Count);
double previous = double.NegativeInfinity;
for (int index = 0; index < source.Count; index++)
{
if (!IsFinite(source[index]) || source[index] <= previous)
throw new ArgumentException("PathS stations must be finite and strictly increasing.", parameterName);
copy.Add(source[index]);
previous = source[index];
}
return new ReadOnlyCollection(copy);
}
private static IReadOnlyList CopyFiniteNonnegative(IReadOnlyList source, int expectedCount,
string parameterName)
{
if (source == null || source.Count != expectedCount)
throw new ArgumentException("Speed limit count must match PathS stations.", parameterName);
var copy = new List(source.Count);
for (int index = 0; index < source.Count; index++)
{
if (!IsFinite(source[index]) || source[index] < 0d)
throw new ArgumentOutOfRangeException(parameterName);
copy.Add(source[index]);
}
return new ReadOnlyCollection(copy);
}
private static bool IsFinite(double value)
{
return !double.IsNaN(value) && !double.IsInfinity(value);
}
}