208 lines
8.7 KiB
C#
208 lines
8.7 KiB
C#
using System;
|
|||
|
|
using System.Collections.Generic;
|
||
|
|
using System.Collections.ObjectModel;
|
||
|
|
using MultiWheelC.TrajectoryPlanning.PathSmoothing.Output.Comparison;
|
||
|
|
|
||
|
|
namespace MultiWheelC.TrajectoryPlanning.PathSmoothing.Output.Visualization;
|
||
|
|
|
||
|
|
/// <summary>SVG 和 PNG 共享的不可变路径平滑报告图形模型。</summary>
|
||
|
|
public sealed class SmoothingFigureModel
|
||
|
|
{
|
||
|
|
internal SmoothingFigureModel(
|
||
|
|
string scenarioId,
|
||
|
|
string scenarioLabel,
|
||
|
|
double worldXMinMeters,
|
||
|
|
double worldXMaxMeters,
|
||
|
|
double worldYMinMeters,
|
||
|
|
double worldYMaxMeters,
|
||
|
|
double pathPanelX,
|
||
|
|
double pathPanelY,
|
||
|
|
double pathPanelWidth,
|
||
|
|
double pathPanelHeight,
|
||
|
|
double curvaturePanelX,
|
||
|
|
double curvaturePanelY,
|
||
|
|
double curvaturePanelWidth,
|
||
|
|
double curvaturePanelHeight,
|
||
|
|
double metricsPanelX,
|
||
|
|
double metricsPanelY,
|
||
|
|
double metricsPanelWidth,
|
||
|
|
double metricsPanelHeight,
|
||
|
|
IReadOnlyList<SmoothingFigureObstacle> obstacles,
|
||
|
|
IReadOnlyList<SmoothingFigureSeries> series,
|
||
|
|
IReadOnlyList<SmoothingFigureMetricRow> metricRows,
|
||
|
|
SmoothingFigurePoint start,
|
||
|
|
SmoothingFigurePoint goal)
|
||
|
|
{
|
||
|
|
ScenarioId = scenarioId ?? string.Empty;
|
||
|
|
ScenarioLabel = scenarioLabel ?? string.Empty;
|
||
|
|
WorldXMinMeters = worldXMinMeters;
|
||
|
|
WorldXMaxMeters = worldXMaxMeters;
|
||
|
|
WorldYMinMeters = worldYMinMeters;
|
||
|
|
WorldYMaxMeters = worldYMaxMeters;
|
||
|
|
PathPanelX = pathPanelX;
|
||
|
|
PathPanelY = pathPanelY;
|
||
|
|
PathPanelWidth = pathPanelWidth;
|
||
|
|
PathPanelHeight = pathPanelHeight;
|
||
|
|
CurvaturePanelX = curvaturePanelX;
|
||
|
|
CurvaturePanelY = curvaturePanelY;
|
||
|
|
CurvaturePanelWidth = curvaturePanelWidth;
|
||
|
|
CurvaturePanelHeight = curvaturePanelHeight;
|
||
|
|
MetricsPanelX = metricsPanelX;
|
||
|
|
MetricsPanelY = metricsPanelY;
|
||
|
|
MetricsPanelWidth = metricsPanelWidth;
|
||
|
|
MetricsPanelHeight = metricsPanelHeight;
|
||
|
|
Obstacles = Copy(obstacles);
|
||
|
|
Series = Copy(series);
|
||
|
|
MetricRows = Copy(metricRows);
|
||
|
|
Start = start ?? throw new ArgumentNullException(nameof(start));
|
||
|
|
Goal = goal ?? throw new ArgumentNullException(nameof(goal));
|
||
|
|
}
|
||
|
|
|
||
|
|
public string ScenarioId { get; }
|
||
|
|
public string ScenarioLabel { get; }
|
||
|
|
public double FigureWidthPoints => IeeeFigureStyle.FigureWidthPoints;
|
||
|
|
public double FigureHeightPoints => IeeeFigureStyle.FigureHeightPoints;
|
||
|
|
public string PathPanelLabel => "(a)";
|
||
|
|
public string CurvaturePanelLabel => "(b)";
|
||
|
|
public string MetricsPanelLabel => "(c)";
|
||
|
|
public double WorldXMinMeters { get; }
|
||
|
|
public double WorldXMaxMeters { get; }
|
||
|
|
public double WorldYMinMeters { get; }
|
||
|
|
public double WorldYMaxMeters { get; }
|
||
|
|
public double PathPanelX { get; }
|
||
|
|
public double PathPanelY { get; }
|
||
|
|
public double PathPanelWidth { get; }
|
||
|
|
public double PathPanelHeight { get; }
|
||
|
|
public double CurvaturePanelX { get; }
|
||
|
|
public double CurvaturePanelY { get; }
|
||
|
|
public double CurvaturePanelWidth { get; }
|
||
|
|
public double CurvaturePanelHeight { get; }
|
||
|
|
public double MetricsPanelX { get; }
|
||
|
|
public double MetricsPanelY { get; }
|
||
|
|
public double MetricsPanelWidth { get; }
|
||
|
|
public double MetricsPanelHeight { get; }
|
||
|
|
public double PathScaleX { get; internal set; }
|
||
|
|
public double PathScaleY { get; internal set; }
|
||
|
|
public IReadOnlyList<SmoothingFigureObstacle> Obstacles { get; }
|
||
|
|
public IReadOnlyList<SmoothingFigureSeries> Series { get; }
|
||
|
|
public IReadOnlyList<SmoothingFigureLegendEntry> LegendEntries => BuildLegend(Series);
|
||
|
|
public IReadOnlyList<SmoothingFigureMetricRow> MetricRows { get; }
|
||
|
|
public SmoothingFigurePoint Start { get; }
|
||
|
|
public SmoothingFigurePoint Goal { get; }
|
||
|
|
|
||
|
|
internal SmoothingFigureModel WithAdditionalSeries(SmoothingFigureSeries series)
|
||
|
|
{
|
||
|
|
if (series == null) throw new ArgumentNullException(nameof(series));
|
||
|
|
var combined = new List<SmoothingFigureSeries>(Series.Count + 1);
|
||
|
|
for (int index = 0; index < Series.Count; index++)
|
||
|
|
{
|
||
|
|
if (Series[index].Key == series.Key)
|
||
|
|
throw new ArgumentException("Figure series keys must be unique.", nameof(series));
|
||
|
|
combined.Add(Series[index]);
|
||
|
|
}
|
||
|
|
combined.Add(series);
|
||
|
|
var copy = new SmoothingFigureModel(
|
||
|
|
ScenarioId, ScenarioLabel, WorldXMinMeters, WorldXMaxMeters, WorldYMinMeters, WorldYMaxMeters,
|
||
|
|
PathPanelX, PathPanelY, PathPanelWidth, PathPanelHeight,
|
||
|
|
CurvaturePanelX, CurvaturePanelY, CurvaturePanelWidth, CurvaturePanelHeight,
|
||
|
|
MetricsPanelX, MetricsPanelY, MetricsPanelWidth, MetricsPanelHeight,
|
||
|
|
Obstacles, combined, MetricRows, Start, Goal)
|
||
|
|
{
|
||
|
|
PathScaleX = PathScaleX,
|
||
|
|
PathScaleY = PathScaleY,
|
||
|
|
};
|
||
|
|
return copy;
|
||
|
|
}
|
||
|
|
|
||
|
|
private static IReadOnlyList<T> Copy<T>(IReadOnlyList<T> source)
|
||
|
|
{
|
||
|
|
var copy = new List<T>(source == null ? 0 : source.Count);
|
||
|
|
if (source != null) for (int index = 0; index < source.Count; index++) copy.Add(source[index]);
|
||
|
|
return new ReadOnlyCollection<T>(copy);
|
||
|
|
}
|
||
|
|
|
||
|
|
private static IReadOnlyList<SmoothingFigureLegendEntry> BuildLegend(IReadOnlyList<SmoothingFigureSeries> series)
|
||
|
|
{
|
||
|
|
var legend = new List<SmoothingFigureLegendEntry>(series == null ? 0 : series.Count);
|
||
|
|
if (series != null)
|
||
|
|
{
|
||
|
|
for (int index = 0; index < series.Count; index++)
|
||
|
|
legend.Add(new SmoothingFigureLegendEntry(series[index].Label, series[index].Color, series[index].DashArray));
|
||
|
|
}
|
||
|
|
return new ReadOnlyCollection<SmoothingFigureLegendEntry>(legend);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public sealed class SmoothingFigurePoint
|
||
|
|
{
|
||
|
|
public SmoothingFigurePoint(double xMeters, double yMeters, double arcLengthMeters, double vehicleCurvaturePerMeter)
|
||
|
|
{
|
||
|
|
X = xMeters; Y = yMeters; ArcLength = arcLengthMeters; VehicleCurvature = vehicleCurvaturePerMeter;
|
||
|
|
}
|
||
|
|
public double X { get; }
|
||
|
|
public double Y { get; }
|
||
|
|
public double ArcLength { get; }
|
||
|
|
public double VehicleCurvature { get; }
|
||
|
|
}
|
||
|
|
|
||
|
|
public sealed class SmoothingFigureObstacle
|
||
|
|
{
|
||
|
|
public SmoothingFigureObstacle(double xMeters, double yMeters, double widthMeters, double heightMeters)
|
||
|
|
{
|
||
|
|
X = xMeters; Y = yMeters; Width = widthMeters; Height = heightMeters;
|
||
|
|
}
|
||
|
|
public double X { get; }
|
||
|
|
public double Y { get; }
|
||
|
|
public double Width { get; }
|
||
|
|
public double Height { get; }
|
||
|
|
}
|
||
|
|
|
||
|
|
public sealed class SmoothingFigureSeries
|
||
|
|
{
|
||
|
|
internal SmoothingFigureSeries(SmoothingMethod? method, string key, string label, PathSmoothingStatus status, string color, string dashArray,
|
||
|
|
bool isRawPathBaseline, IReadOnlyList<SmoothingFigurePoint> points, IReadOnlyList<SmoothingFigurePoint> violationMarkers)
|
||
|
|
{
|
||
|
|
Method = method; Key = key ?? string.Empty; Label = label ?? string.Empty; Status = status; Color = color ?? string.Empty;
|
||
|
|
DashArray = dashArray ?? string.Empty; IsRawPathBaseline = isRawPathBaseline; Points = Copy(points); ViolationMarkers = Copy(violationMarkers);
|
||
|
|
}
|
||
|
|
public SmoothingMethod? Method { get; }
|
||
|
|
public string Key { get; }
|
||
|
|
public string Label { get; }
|
||
|
|
public PathSmoothingStatus Status { get; }
|
||
|
|
public string Color { get; }
|
||
|
|
public string DashArray { get; }
|
||
|
|
public bool IsRawPathBaseline { get; }
|
||
|
|
public bool IsCurveVisible => Points.Count > 0;
|
||
|
|
public IReadOnlyList<SmoothingFigurePoint> Points { get; }
|
||
|
|
public IReadOnlyList<SmoothingFigurePoint> ViolationMarkers { get; }
|
||
|
|
private static IReadOnlyList<T> Copy<T>(IReadOnlyList<T> source)
|
||
|
|
{
|
||
|
|
var copy = new List<T>(source == null ? 0 : source.Count);
|
||
|
|
if (source != null) for (int index = 0; index < source.Count; index++) copy.Add(source[index]);
|
||
|
|
return new ReadOnlyCollection<T>(copy);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public sealed class SmoothingFigureLegendEntry
|
||
|
|
{
|
||
|
|
internal SmoothingFigureLegendEntry(string label, string color, string dashArray) { Label = label ?? string.Empty; Color = color ?? string.Empty; DashArray = dashArray ?? string.Empty; }
|
||
|
|
public string Label { get; }
|
||
|
|
public string Color { get; }
|
||
|
|
public string DashArray { get; }
|
||
|
|
}
|
||
|
|
|
||
|
|
public sealed class SmoothingFigureMetricRow
|
||
|
|
{
|
||
|
|
internal SmoothingFigureMetricRow(string method, string label, PathSmoothingStatus status, PathQualityMetrics metrics,
|
||
|
|
SmoothingTimingSummary timing)
|
||
|
|
{
|
||
|
|
Method = method ?? string.Empty; Label = label ?? string.Empty; Status = status; Metrics = metrics ?? new PathQualityMetrics();
|
||
|
|
Timing = timing;
|
||
|
|
}
|
||
|
|
public string Method { get; }
|
||
|
|
public string Label { get; }
|
||
|
|
public PathSmoothingStatus Status { get; }
|
||
|
|
public PathQualityMetrics Metrics { get; }
|
||
|
|
public SmoothingTimingSummary Timing { get; }
|
||
|
|
}
|