554 lines
23 KiB
C#
554 lines
23 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Collections.ObjectModel;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using MultiWheelC.TrajectoryPlanning.CoarsePath;
|
|
using MultiWheelC.TrajectoryPlanning.CoarsePath.Facade;
|
|
using MultiWheelC.TrajectoryPlanning.EMPlanner;
|
|
using MultiWheelC.TrajectoryPlanning.Mapping;
|
|
using MultiWheelC.TrajectoryPlanning.PathSmoothing;
|
|
using MultiWheelC.TrajectoryPlanning.PathSmoothing.Facade;
|
|
|
|
namespace MultiWheelC.TrajectoryPlanning.TrajectoryObservation;
|
|
|
|
public sealed class TrajectoryObservationBootstrapResult
|
|
{
|
|
private readonly VehicleParameters vehicle;
|
|
|
|
private TrajectoryObservationBootstrapResult(CoarsePathPlanningJob job, CoarsePathPlanningJobResult coarse,
|
|
PathSmoothingResult smoothedPath, IReadOnlyList<DirectionSegmentView> segments, string failureReason)
|
|
{
|
|
Job = job ?? throw new ArgumentNullException(nameof(job));
|
|
vehicle = CopyVehicle(job.Vehicle);
|
|
CoarseResult = coarse;
|
|
SmoothedPath = smoothedPath;
|
|
Segments = CopySegments(segments);
|
|
FailureReason = failureReason ?? string.Empty;
|
|
Succeeded = coarse != null && smoothedPath != null && Segments.Count > 0 && FailureReason.Length == 0;
|
|
}
|
|
|
|
public bool Succeeded { get; }
|
|
|
|
public CoarsePathPlanningJob Job { get; }
|
|
|
|
public VehicleParameters Vehicle => CopyVehicle(vehicle);
|
|
|
|
public CoarsePathPlanningJobResult CoarseResult { get; }
|
|
|
|
public PathSmoothingResult SmoothedPath { get; }
|
|
|
|
public IReadOnlyList<DirectionSegmentView> Segments { get; }
|
|
|
|
public string FailureReason { get; }
|
|
|
|
public PlanningGridMap Map => CoarseResult?.MapResult?.Map;
|
|
|
|
public static TrajectoryObservationBootstrapResult FromFailure(CoarsePathPlanningJob job,
|
|
CoarsePathPlanningJobResult coarse, PathSmoothingResult smoothedPath, string failureReason)
|
|
{
|
|
return new TrajectoryObservationBootstrapResult(job, coarse, smoothedPath,
|
|
Array.Empty<DirectionSegmentView>(), string.IsNullOrEmpty(failureReason) ? "Planning bootstrap failed." : failureReason);
|
|
}
|
|
|
|
public static TrajectoryObservationBootstrapResult Success(CoarsePathPlanningJob job,
|
|
CoarsePathPlanningJobResult coarse, PathSmoothingResult smoothedPath,
|
|
IReadOnlyList<DirectionSegmentView> segments)
|
|
{
|
|
if (coarse == null) throw new ArgumentNullException(nameof(coarse));
|
|
if (smoothedPath == null) throw new ArgumentNullException(nameof(smoothedPath));
|
|
if (segments == null || segments.Count == 0)
|
|
throw new ArgumentException("A successful bootstrap requires reference segments.", nameof(segments));
|
|
return new TrajectoryObservationBootstrapResult(job, coarse, smoothedPath, segments, string.Empty);
|
|
}
|
|
|
|
private static IReadOnlyList<DirectionSegmentView> CopySegments(IReadOnlyList<DirectionSegmentView> source)
|
|
{
|
|
var copy = new List<DirectionSegmentView>(source == null ? 0 : source.Count);
|
|
if (source != null)
|
|
{
|
|
for (int index = 0; index < source.Count; index++)
|
|
copy.Add(source[index]);
|
|
}
|
|
return new ReadOnlyCollection<DirectionSegmentView>(copy);
|
|
}
|
|
|
|
private static VehicleParameters CopyVehicle(VehicleParameters source)
|
|
{
|
|
if (source == null) return null;
|
|
return new VehicleParameters
|
|
{
|
|
LengthMeters = source.LengthMeters,
|
|
WidthMeters = source.WidthMeters,
|
|
SafetyMarginMeters = source.SafetyMarginMeters,
|
|
MaximumCurvaturePerMeter = source.MaximumCurvaturePerMeter,
|
|
MinimumTurningRadiusMeters = source.MinimumTurningRadiusMeters,
|
|
};
|
|
}
|
|
}
|
|
|
|
public sealed class TrajectoryObservationBootstrapper
|
|
{
|
|
private readonly CoarsePathPlanningService coarseService;
|
|
private readonly PathSmoothingService smoothingService;
|
|
|
|
public TrajectoryObservationBootstrapper()
|
|
: this(new CoarsePathPlanningService(), new PathSmoothingService())
|
|
{
|
|
}
|
|
|
|
public TrajectoryObservationBootstrapper(CoarsePathPlanningService coarseService,
|
|
PathSmoothingService smoothingService)
|
|
{
|
|
this.coarseService = coarseService ?? throw new ArgumentNullException(nameof(coarseService));
|
|
this.smoothingService = smoothingService ?? throw new ArgumentNullException(nameof(smoothingService));
|
|
}
|
|
|
|
public TrajectoryObservationBootstrapResult Bootstrap(CoarsePathPlanningJob job,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
if (job == null) throw new ArgumentNullException(nameof(job));
|
|
|
|
CoarsePathPlanningJobResult coarse = coarseService.Plan(job, cancellationToken);
|
|
if (coarse.PlanningResult.Status != PlanningStatus.Success)
|
|
return TrajectoryObservationBootstrapResult.FromFailure(
|
|
job, coarse, null, "Coarse planning status: " + coarse.PlanningResult.Status);
|
|
|
|
var smoothingRequest = new PathSmoothingRequest(
|
|
CopyFiniteClearance(coarse.PlanningResult.Path, coarse.MapResult.Map),
|
|
coarse.PlanningResult.Segments, coarse.MapResult.Map, job.Vehicle,
|
|
new PathSmoothingConfiguration());
|
|
PathSmoothingResult smooth = smoothingService.Smooth(smoothingRequest, cancellationToken);
|
|
if (!IsPublishedSmoothingStatus(smooth.Status))
|
|
return TrajectoryObservationBootstrapResult.FromFailure(
|
|
job, coarse, smooth, smooth.Diagnostics.TerminationReason);
|
|
return TrajectoryObservationBootstrapResult.Success(job, coarse, smooth, ReferencePathSegmenter.Create(smooth));
|
|
}
|
|
|
|
private static IReadOnlyList<CoarsePathPoint> CopyFiniteClearance(IReadOnlyList<CoarsePathPoint> path,
|
|
PlanningGridMap map)
|
|
{
|
|
if (path == null) throw new ArgumentNullException(nameof(path));
|
|
if (map == null) throw new ArgumentNullException(nameof(map));
|
|
|
|
double widthMeters = (map.Bounds.XMax - map.Bounds.XMin) / 1000d;
|
|
double heightMeters = (map.Bounds.YMax - map.Bounds.YMin) / 1000d;
|
|
double mapDiagonalMeters = Math.Sqrt(widthMeters * widthMeters + heightMeters * heightMeters);
|
|
var copy = new List<CoarsePathPoint>(path.Count);
|
|
for (int index = 0; index < path.Count; index++)
|
|
{
|
|
CoarsePathPoint point = path[index] ?? throw new ArgumentException(
|
|
"The coarse path cannot contain null points.", nameof(path));
|
|
double clearance = double.IsPositiveInfinity(point.BodyClearance)
|
|
? mapDiagonalMeters
|
|
: point.BodyClearance;
|
|
copy.Add(new CoarsePathPoint(point.X, point.Y, point.Heading, point.UnwrappedHeading,
|
|
point.ArcLength, point.Direction, point.VehicleCurvature, clearance,
|
|
point.IsGearSwitchPoint, point.Source));
|
|
}
|
|
return new ReadOnlyCollection<CoarsePathPoint>(copy);
|
|
}
|
|
|
|
private static bool IsPublishedSmoothingStatus(PathSmoothingStatus status)
|
|
{
|
|
return status == PathSmoothingStatus.Complete ||
|
|
status == PathSmoothingStatus.PartialImprovement ||
|
|
status == PathSmoothingStatus.NotNeeded ||
|
|
status == PathSmoothingStatus.Unchanged;
|
|
}
|
|
}
|
|
|
|
public sealed class TrajectoryObservationObservation
|
|
{
|
|
internal TrajectoryObservationObservation(DateTimeOffset observedAtUtc, VehicleMotionState vehicleState,
|
|
EmTrajectory publishedTrajectory, EmTrajectoryPoint selectedPoint, TrajectoryControlCommand command,
|
|
TrajectoryExecutionState executorState)
|
|
{
|
|
ObservedAtUtc = observedAtUtc;
|
|
VehicleState = vehicleState ?? throw new ArgumentNullException(nameof(vehicleState));
|
|
PublishedTrajectory = publishedTrajectory;
|
|
SelectedPoint = selectedPoint;
|
|
Command = command;
|
|
ExecutorState = executorState;
|
|
}
|
|
|
|
public DateTimeOffset ObservedAtUtc { get; }
|
|
|
|
public VehicleMotionState VehicleState { get; }
|
|
|
|
public EmTrajectory PublishedTrajectory { get; }
|
|
|
|
public EmTrajectoryPoint SelectedPoint { get; }
|
|
|
|
public TrajectoryControlCommand Command { get; }
|
|
|
|
public TrajectoryExecutionState ExecutorState { get; }
|
|
}
|
|
|
|
public sealed class TrajectoryObservationController
|
|
{
|
|
private readonly TrajectoryObservationBootstrapResult bootstrap;
|
|
private readonly TrajectoryObservationSettings settings;
|
|
private readonly EmPlannerConfiguration configuration;
|
|
private readonly IEmPlanningService planningService;
|
|
private readonly TrajectoryObservationSegmentTracker segmentTracker;
|
|
private EmPlanningCoordinator coordinator;
|
|
private TrajectoryExecutor executor;
|
|
private EmTrajectory previousTrajectoryForVisualization;
|
|
private readonly string sessionId;
|
|
private long cycleId;
|
|
private int plannedSegmentIndex;
|
|
private bool planAttemptedForActiveSegment;
|
|
|
|
public TrajectoryObservationController(TrajectoryObservationBootstrapResult bootstrap,
|
|
TrajectoryObservationSettings settings, IEmPlanningService planningService, string sessionId)
|
|
{
|
|
this.bootstrap = bootstrap ?? throw new ArgumentNullException(nameof(bootstrap));
|
|
if (!bootstrap.Succeeded)
|
|
throw new ArgumentException("A successful planning bootstrap is required.", nameof(bootstrap));
|
|
if (settings == null) throw new ArgumentNullException(nameof(settings));
|
|
if (planningService == null) throw new ArgumentNullException(nameof(planningService));
|
|
if (string.IsNullOrWhiteSpace(sessionId)) throw new ArgumentException("A session ID is required.", nameof(sessionId));
|
|
|
|
TrajectoryObservationSettings snapshot = settings.CreateValidatedSnapshot();
|
|
this.settings = snapshot;
|
|
configuration = EmPlannerConfiguration.CreateDefault();
|
|
configuration.Scheduling.ReplanPeriodSeconds = snapshot.ReplanPeriodSeconds;
|
|
configuration.Scheduling.SolverTimeoutSeconds = snapshot.SolverTimeoutSeconds;
|
|
configuration.Solver.MaximumOsqpIterations = snapshot.MaximumOsqpIterations;
|
|
configuration.Scheduling.TimeHorizonSeconds = snapshot.TimeHorizonSeconds;
|
|
configuration.Scheduling.OutputTimeStepSeconds = snapshot.OutputTimeStepSeconds;
|
|
this.planningService = planningService;
|
|
coordinator = new EmPlanningCoordinator(planningService);
|
|
executor = new TrajectoryExecutor(configuration);
|
|
segmentTracker = new TrajectoryObservationSegmentTracker(bootstrap.Segments, snapshot,
|
|
configuration.Longitudinal.StopSpeedToleranceMetersPerSecond);
|
|
this.sessionId = sessionId;
|
|
}
|
|
|
|
public EmTrajectory PublishedTrajectory => coordinator.PublishedTrajectory;
|
|
|
|
public DirectionSegmentView ActiveSegment => bootstrap.Segments[segmentTracker.State.ActiveSegmentIndex];
|
|
|
|
public TrajectoryObservationSegmentState SegmentState => segmentTracker.State;
|
|
|
|
public EmTrajectory PreviousTrajectoryForVisualization => previousTrajectoryForVisualization;
|
|
|
|
internal EmPlannerConfiguration CreateEffectiveConfigurationSnapshot()
|
|
{
|
|
return configuration.Copy();
|
|
}
|
|
|
|
public TrajectoryObservationDiagnostic CreateConfigurationDiagnostic()
|
|
{
|
|
return TrajectoryObservationDiagnostics.CreateConfiguration(configuration);
|
|
}
|
|
|
|
public bool ShouldStartCycle(DateTimeOffset now)
|
|
{
|
|
if (settings.PlanningScope == EmPlanningScope.FullDirectionSegment)
|
|
{
|
|
if (plannedSegmentIndex != ActiveSegment.SegmentIndex)
|
|
{
|
|
plannedSegmentIndex = ActiveSegment.SegmentIndex;
|
|
planAttemptedForActiveSegment = false;
|
|
}
|
|
return !planAttemptedForActiveSegment;
|
|
}
|
|
return coordinator.ShouldStartCycle(now);
|
|
}
|
|
|
|
public Task<PlanningCycleResult> StartCycle(DateTimeOffset now, VehicleMotionState state,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
if (state == null) throw new ArgumentNullException(nameof(state));
|
|
|
|
planAttemptedForActiveSegment = true;
|
|
plannedSegmentIndex = ActiveSegment.SegmentIndex;
|
|
long currentCycleId = Interlocked.Increment(ref cycleId);
|
|
EmTrajectory previousTrajectory = coordinator.PublishedTrajectory;
|
|
var request = new EmPlanningRequest(
|
|
bootstrap.SmoothedPath, bootstrap.Map, bootstrap.Vehicle, state, configuration,
|
|
ActiveSegment.SegmentIndex, previousTrajectory, now, now,
|
|
sessionId + "-trajectory-" + currentCycleId, sessionId + "-reference",
|
|
previousTrajectory?.Metadata.TrajectoryId ?? string.Empty,
|
|
EmMotionModel.NonholonomicForwardReverse, settings.PlanningScope);
|
|
return coordinator.PlanLatestAsync(new PlanningCycleInput(request, now), cancellationToken);
|
|
}
|
|
|
|
public bool TryAdvanceSegment(DateTimeOffset now, VehicleMotionState state)
|
|
{
|
|
if (state == null) throw new ArgumentNullException(nameof(state));
|
|
|
|
TrajectoryObservationSegmentUpdate update = segmentTracker.Update(now, state, coordinator.PublishedTrajectory);
|
|
if (!update.Advanced)
|
|
return false;
|
|
|
|
previousTrajectoryForVisualization = coordinator.PublishedTrajectory;
|
|
coordinator = new EmPlanningCoordinator(planningService);
|
|
executor = new TrajectoryExecutor(configuration);
|
|
plannedSegmentIndex = segmentTracker.State.ActiveSegmentIndex;
|
|
planAttemptedForActiveSegment = false;
|
|
return true;
|
|
}
|
|
|
|
public TrajectoryObservationObservation Observe(DateTimeOffset now, VehicleMotionState state)
|
|
{
|
|
if (state == null) throw new ArgumentNullException(nameof(state));
|
|
|
|
EmTrajectory trajectory = coordinator.PublishedTrajectory;
|
|
if (trajectory == null)
|
|
return new TrajectoryObservationObservation(now, state, null, null, null, null);
|
|
|
|
TrajectoryObservationSegmentState segmentState = segmentTracker.State;
|
|
TravelDirection currentDirection = ActiveSegment.Direction;
|
|
bool waitingForDirection = segmentState.Phase == TrajectoryObservationSegmentPhase.WaitingForStop ||
|
|
segmentState.Phase == TrajectoryObservationSegmentPhase.WaitingForDirection;
|
|
TravelDirection desiredDirection = waitingForDirection && segmentState.ExpectedDirection.HasValue
|
|
? segmentState.ExpectedDirection.Value
|
|
: currentDirection;
|
|
TrajectoryControlCommand command = executor.UpdateCommand(now, state, trajectory,
|
|
desiredDirection, currentDirection, !waitingForDirection);
|
|
TrajectoryExecutionState executorState = executor.State;
|
|
return new TrajectoryObservationObservation(now, state, trajectory, executorState.SelectedPoint,
|
|
command, executorState);
|
|
}
|
|
}
|
|
|
|
public sealed class TrajectoryObservationLoopTick
|
|
{
|
|
internal TrajectoryObservationLoopTick(TrajectoryObservationObservation observation,
|
|
PlanningCycleResult latestCycle, TimeSpan latestPlanningElapsed, bool planningInFlight,
|
|
bool planningStarted, bool planningCompleted, bool segmentAdvanced,
|
|
TrajectoryObservationSegmentState segmentState)
|
|
{
|
|
Observation = observation ?? throw new ArgumentNullException(nameof(observation));
|
|
LatestCycle = latestCycle;
|
|
LatestPlanningElapsed = latestPlanningElapsed;
|
|
PlanningInFlight = planningInFlight;
|
|
PlanningStarted = planningStarted;
|
|
PlanningCompleted = planningCompleted;
|
|
SegmentAdvanced = segmentAdvanced;
|
|
SegmentState = segmentState ?? throw new ArgumentNullException(nameof(segmentState));
|
|
}
|
|
|
|
public TrajectoryObservationObservation Observation { get; }
|
|
|
|
public PlanningCycleResult LatestCycle { get; }
|
|
|
|
public TimeSpan LatestPlanningElapsed { get; }
|
|
|
|
public bool PlanningInFlight { get; }
|
|
|
|
public bool PlanningStarted { get; }
|
|
|
|
public bool PlanningCompleted { get; }
|
|
|
|
public bool SegmentAdvanced { get; }
|
|
|
|
public TrajectoryObservationSegmentState SegmentState { get; }
|
|
|
|
public bool ShouldLog => true;
|
|
}
|
|
|
|
public sealed class TrajectoryObservationLoop
|
|
{
|
|
private readonly TrajectoryObservationController controller;
|
|
private Task<PlanningCycleResult> planningTask;
|
|
private DateTimeOffset planningStartedAtUtc;
|
|
private PlanningCycleResult latestCycle;
|
|
private TimeSpan latestPlanningElapsed;
|
|
|
|
public TrajectoryObservationLoop(TrajectoryObservationController controller)
|
|
{
|
|
this.controller = controller ?? throw new ArgumentNullException(nameof(controller));
|
|
}
|
|
|
|
public TrajectoryObservationLoopTick Tick(DateTimeOffset now, VehicleMotionState state,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
if (state == null) throw new ArgumentNullException(nameof(state));
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
|
|
bool planningCompleted = ConsumeCompletedPlanning(now);
|
|
bool segmentAdvanced = planningTask == null && controller.TryAdvanceSegment(now, state);
|
|
if (segmentAdvanced)
|
|
latestCycle = null;
|
|
bool planningStarted = false;
|
|
if (planningTask == null && controller.ShouldStartCycle(now))
|
|
{
|
|
planningStarted = true;
|
|
planningStartedAtUtc = now;
|
|
planningTask = controller.StartCycle(now, state, cancellationToken);
|
|
planningCompleted |= ConsumeCompletedPlanning(now);
|
|
}
|
|
|
|
TrajectoryObservationObservation observation = controller.Observe(now, state);
|
|
return new TrajectoryObservationLoopTick(observation, latestCycle, latestPlanningElapsed,
|
|
planningTask != null, planningStarted, planningCompleted, segmentAdvanced, controller.SegmentState);
|
|
}
|
|
|
|
private bool ConsumeCompletedPlanning(DateTimeOffset observedAtUtc)
|
|
{
|
|
Task<PlanningCycleResult> completed = planningTask;
|
|
if (completed == null || !completed.IsCompleted) return false;
|
|
|
|
latestCycle = completed.GetAwaiter().GetResult();
|
|
TimeSpan elapsed = observedAtUtc - planningStartedAtUtc;
|
|
latestPlanningElapsed = elapsed < TimeSpan.Zero ? TimeSpan.Zero : elapsed;
|
|
planningTask = null;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
public enum TrajectoryObservationSessionEndReason
|
|
{
|
|
BootstrapFailure,
|
|
RuntimeFault,
|
|
Cancellation,
|
|
}
|
|
|
|
public static class TrajectoryObservationSessionLifecycle
|
|
{
|
|
public static bool ShouldClearLayers(TrajectoryObservationSessionEndReason reason)
|
|
{
|
|
switch (reason)
|
|
{
|
|
case TrajectoryObservationSessionEndReason.BootstrapFailure:
|
|
return false;
|
|
case TrajectoryObservationSessionEndReason.RuntimeFault:
|
|
case TrajectoryObservationSessionEndReason.Cancellation:
|
|
return true;
|
|
default:
|
|
throw new ArgumentOutOfRangeException(nameof(reason));
|
|
}
|
|
}
|
|
}
|
|
|
|
public sealed class TrajectoryObservationRuntimeState
|
|
{
|
|
public const string GearSwitchWaitingNotice =
|
|
"等待真实档位/方向确认;观察模式不会推进下一方向段";
|
|
|
|
private TrajectoryObservationRuntimeState(bool waitingAtGearSwitch)
|
|
{
|
|
WaitingAtGearSwitch = waitingAtGearSwitch;
|
|
WorldNotice = waitingAtGearSwitch ? GearSwitchWaitingNotice : string.Empty;
|
|
}
|
|
|
|
public bool WaitingAtGearSwitch { get; }
|
|
|
|
public string WorldNotice { get; }
|
|
|
|
public static TrajectoryObservationRuntimeState Create(DateTimeOffset now, EmTrajectory trajectory)
|
|
{
|
|
if (trajectory == null || trajectory.Metadata.TerminalType != EmTerminalType.GearSwitch)
|
|
return new TrajectoryObservationRuntimeState(false);
|
|
|
|
EmTrajectoryPoint finalPoint = trajectory.Points[trajectory.Points.Count - 1];
|
|
bool waiting = (now - trajectory.Metadata.EffectiveAtUtc).TotalSeconds >= finalPoint.TimeFromStart;
|
|
return new TrajectoryObservationRuntimeState(waiting);
|
|
}
|
|
}
|
|
|
|
public sealed class TrajectoryObservationLsSample
|
|
{
|
|
public TrajectoryObservationLsSample(double pathS, double lateralOffset)
|
|
{
|
|
PathS = pathS;
|
|
LateralOffset = lateralOffset;
|
|
}
|
|
|
|
public double PathS { get; }
|
|
|
|
public double LateralOffset { get; }
|
|
}
|
|
|
|
public sealed class TrajectoryObservationStSample
|
|
{
|
|
public TrajectoryObservationStSample(double timeFromStart, double pathS)
|
|
{
|
|
TimeFromStart = timeFromStart;
|
|
PathS = pathS;
|
|
}
|
|
|
|
public double TimeFromStart { get; }
|
|
|
|
public double PathS { get; }
|
|
}
|
|
|
|
public sealed class TrajectoryObservationSpeedSample
|
|
{
|
|
public TrajectoryObservationSpeedSample(double timeFromStart, double signedLongitudinalVelocity)
|
|
{
|
|
TimeFromStart = timeFromStart;
|
|
SignedLongitudinalVelocity = signedLongitudinalVelocity;
|
|
}
|
|
|
|
public double TimeFromStart { get; }
|
|
|
|
public double SignedLongitudinalVelocity { get; }
|
|
}
|
|
|
|
public sealed class TrajectoryObservationCharts
|
|
{
|
|
private TrajectoryObservationCharts(IReadOnlyList<TrajectoryObservationLsSample> lsSamples,
|
|
IReadOnlyList<TrajectoryObservationStSample> stSamples,
|
|
IReadOnlyList<TrajectoryObservationSpeedSample> speedSamples, int failedProjectionCount)
|
|
{
|
|
LsSamples = new ReadOnlyCollection<TrajectoryObservationLsSample>(
|
|
new List<TrajectoryObservationLsSample>(lsSamples));
|
|
StSamples = new ReadOnlyCollection<TrajectoryObservationStSample>(
|
|
new List<TrajectoryObservationStSample>(stSamples));
|
|
SpeedSamples = new ReadOnlyCollection<TrajectoryObservationSpeedSample>(
|
|
new List<TrajectoryObservationSpeedSample>(speedSamples));
|
|
FailedProjectionCount = failedProjectionCount;
|
|
}
|
|
|
|
public IReadOnlyList<TrajectoryObservationLsSample> LsSamples { get; }
|
|
|
|
public IReadOnlyList<TrajectoryObservationStSample> StSamples { get; }
|
|
|
|
public IReadOnlyList<TrajectoryObservationSpeedSample> SpeedSamples { get; }
|
|
|
|
public int FailedProjectionCount { get; }
|
|
|
|
public static TrajectoryObservationCharts Build(EmTrajectory trajectory, DirectionSegmentView segment,
|
|
double maximumProjectionDistanceMeters)
|
|
{
|
|
if (trajectory == null) throw new ArgumentNullException(nameof(trajectory));
|
|
if (segment == null) throw new ArgumentNullException(nameof(segment));
|
|
if (double.IsNaN(maximumProjectionDistanceMeters) || double.IsInfinity(maximumProjectionDistanceMeters) ||
|
|
maximumProjectionDistanceMeters < 0d)
|
|
throw new ArgumentOutOfRangeException(nameof(maximumProjectionDistanceMeters));
|
|
|
|
var ls = new List<TrajectoryObservationLsSample>(trajectory.Points.Count);
|
|
var st = new List<TrajectoryObservationStSample>(trajectory.Points.Count);
|
|
var speed = new List<TrajectoryObservationSpeedSample>(trajectory.Points.Count);
|
|
var projector = new FrenetProjector();
|
|
double seedReferenceS = 0d;
|
|
int failedProjectionCount = 0;
|
|
for (int index = 0; index < trajectory.Points.Count; index++)
|
|
{
|
|
EmTrajectoryPoint point = trajectory.Points[index];
|
|
var pose = new Pose2D(point.X, point.Y, point.Yaw);
|
|
if (projector.TryProject(pose, segment, 0d, segment.LengthMeters,
|
|
maximumProjectionDistanceMeters, seedReferenceS, out FrenetProjection projection))
|
|
{
|
|
ls.Add(new TrajectoryObservationLsSample(
|
|
segment.SourceStartArcLength + projection.ReferenceS, projection.LateralOffset));
|
|
seedReferenceS = projection.ReferenceS;
|
|
}
|
|
else
|
|
{
|
|
failedProjectionCount++;
|
|
}
|
|
|
|
st.Add(new TrajectoryObservationStSample(point.TimeFromStart, point.PathS));
|
|
speed.Add(new TrajectoryObservationSpeedSample(point.TimeFromStart, point.SignedLongitudinalVelocity));
|
|
}
|
|
|
|
return new TrajectoryObservationCharts(ls, st, speed, failedProjectionCount);
|
|
}
|
|
}
|