using System;
using System.Globalization;
using System.Text;
namespace MultiWheelC.TrajectoryPlanning.PathSmoothing.Output.Visualization;
/// 把单张共享图形定义渲染为 UTF-8 XML 可编辑 SVG;轨迹仅由离散点组成。
public sealed class SmoothingSvgRenderer
{
public string Render(SmoothingFigureModel model)
{
if (model == null) throw new ArgumentNullException(nameof(model));
return Render(new SmoothingFigureSetBuilder().Build(model).Figures[1]);
}
public string Render(SmoothingFigureDefinition figure)
{
if (figure == null) throw new ArgumentNullException(nameof(figure));
var svg = new StringBuilder();
svg.Append("\n");
return svg.ToString();
}
private static void AppendTitle(StringBuilder svg, SmoothingFigureDefinition figure)
{
svg.Append("")
.Append(Escape(figure.Title)).Append(" — ").Append(Escape(figure.Model.ScenarioLabel)).Append("\n");
}
private static void AppendOverheadAxes(StringBuilder svg, SmoothingFigureDefinition figure)
{
AppendPlotFrame(svg, figure);
for (int index = 0; index < figure.XTicks.Count; index++)
{
double x = WorldX(figure, figure.XTicks[index]);
AppendGridLine(svg, x, figure.PlotYPoints, x, figure.PlotYPoints + figure.PlotHeightPoints);
svg.Append("").Append(Number(figure.XTicks[index])).Append("\n");
}
for (int index = 0; index < figure.YTicks.Count; index++)
{
double y = WorldY(figure, figure.YTicks[index]);
AppendGridLine(svg, figure.PlotXPoints, y, figure.PlotXPoints + figure.PlotWidthPoints, y);
svg.Append("").Append(Number(figure.YTicks[index])).Append("\n");
}
svg.Append("X (m)\n")
.Append("Y (m)\n");
}
private static void AppendCurvatureAxes(StringBuilder svg, SmoothingFigureDefinition figure)
{
AppendPlotFrame(svg, figure);
for (int index = 0; index < figure.CurvatureArcLengthTicks.Count; index++)
{
double x = CurvatureX(figure, figure.CurvatureArcLengthTicks[index]);
AppendGridLine(svg, x, figure.PlotYPoints, x, figure.PlotYPoints + figure.PlotHeightPoints);
svg.Append("").Append(Number(figure.CurvatureArcLengthTicks[index])).Append("\n");
}
for (int index = 0; index < figure.CurvatureTicks.Count; index++)
{
double y = CurvatureY(figure, figure.CurvatureTicks[index]);
AppendGridLine(svg, figure.PlotXPoints, y, figure.PlotXPoints + figure.PlotWidthPoints, y);
svg.Append("").Append(Number(figure.CurvatureTicks[index])).Append("\n");
}
if (figure.CurvatureMinimumPerMeter < 0d && figure.CurvatureMaximumPerMeter > 0d)
{
double zero = CurvatureY(figure, 0d);
svg.Append("\n");
}
svg.Append("s (m)\n")
.Append("κ (m⁻¹)\n");
}
private static void AppendPlotFrame(StringBuilder svg, SmoothingFigureDefinition figure)
{
svg.Append("\n");
}
private static void AppendGridLine(StringBuilder svg, double x1, double y1, double x2, double y2)
{
svg.Append("\n");
}
private static void AppendObstacles(StringBuilder svg, SmoothingFigureDefinition figure)
{
for (int index = 0; index < figure.Model.Obstacles.Count; index++)
{
SmoothingFigureObstacle obstacle = figure.Model.Obstacles[index];
svg.Append("\n");
}
}
private static void AppendPathPoints(StringBuilder svg, SmoothingFigureDefinition figure)
{
for (int viewIndex = 0; viewIndex < figure.Series.Count; viewIndex++)
{
SmoothingFigureSeriesView view = figure.Series[viewIndex];
AppendPoints(svg, view, point => WorldX(figure, point.X), point => WorldY(figure, point.Y));
for (int markerIndex = 0; markerIndex < view.Series.ViolationMarkers.Count; markerIndex++) AppendCross(svg, figure, view.Series.ViolationMarkers[markerIndex], view.Series.Color);
}
}
private static void AppendCurvaturePoints(StringBuilder svg, SmoothingFigureDefinition figure)
{
for (int viewIndex = 0; viewIndex < figure.Series.Count; viewIndex++)
{
SmoothingFigureSeriesView view = figure.Series[viewIndex];
AppendPoints(svg, view, point => CurvatureX(figure, point.ArcLength), point => CurvatureY(figure, point.VehicleCurvature));
}
}
private static void AppendPoints(StringBuilder svg, SmoothingFigureSeriesView view, Func x, Func y)
{
if (!view.Series.IsCurveVisible) return;
svg.Append("\n");
for (int index = 0; index < view.Series.Points.Count; index++)
{
SmoothingFigurePoint point = view.Series.Points[index];
svg.Append("\n");
}
svg.Append("\n");
}
private static void AppendCross(StringBuilder svg, SmoothingFigureDefinition figure, SmoothingFigurePoint point, string color)
{
double x = WorldX(figure, point.X), y = WorldY(figure, point.Y), radius = 3d;
svg.Append("\n");
}
private static void AppendStartGoal(StringBuilder svg, SmoothingFigureDefinition figure)
{
svg.Append("\n");
double x = WorldX(figure, figure.Model.Goal.X), y = WorldY(figure, figure.Model.Goal.Y), radius = 4d;
svg.Append("\n");
}
private static void AppendLegend(StringBuilder svg, SmoothingFigureDefinition figure)
{
double columnWidth = 198d;
for (int index = 0; index < figure.LegendEntries.Count; index++)
{
SmoothingFigureLegendEntry entry = figure.LegendEntries[index];
int column = index % 2;
int row = index / 2;
double x = figure.PlotXPoints + column * columnWidth;
double y = figure.LegendYPoints + row * 15d;
svg.Append("\n")
.Append(Escape(entry.Label)).Append("\n");
}
}
private static double WorldX(SmoothingFigureDefinition figure, double x) { return figure.PlotXPoints + (x - figure.WorldXMinMeters) * figure.WorldScalePointsPerMeter; }
private static double WorldY(SmoothingFigureDefinition figure, double y) { return figure.PlotYPoints + figure.PlotHeightPoints - (y - figure.WorldYMinMeters) * figure.WorldScalePointsPerMeter; }
private static double CurvatureX(SmoothingFigureDefinition figure, double arcLength) { return figure.PlotXPoints + arcLength / figure.CurvatureArcLengthMaximumMeters * figure.PlotWidthPoints; }
private static double CurvatureY(SmoothingFigureDefinition figure, double curvature) { return figure.PlotYPoints + figure.PlotHeightPoints - (curvature - figure.CurvatureMinimumPerMeter) / (figure.CurvatureMaximumPerMeter - figure.CurvatureMinimumPerMeter) * figure.PlotHeightPoints; }
private static string Number(double value) { return value.ToString("0.###", CultureInfo.InvariantCulture); }
private static string Escape(string value) { return (value ?? string.Empty).Replace("&", "&").Replace("<", "<").Replace(">", ">").Replace("\"", """).Replace("'", "'"); }
}