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); } }