Files
ParkingRobot/ClumsyPilot/ParkrobTrajplanner/Map/Test/Visualization/PlanningMapImageExporter.cs
T

68 lines
4.5 KiB
C#
Raw Normal View History

2026-08-09 22:13:18 +08:00
using System;
using System.IO;
namespace MultiWheelC.TrajectoryPlanning.Mapping;
/// <summary>可选 PNG 导出器;它永远不参与地图构建、障碍物投影或缓存指纹计算。</summary>
public static class PlanningMapImageExporter
{
/// <summary>每个规划栅格在输出图中占用的边长,单位为像素。</summary>
public const int PixelsPerCell = 4;
/// <summary>输出 PNG 单边允许的最大像素数,超过时拒绝导出。</summary>
public const int MaximumImageEdgePixels = 4000;
/// <summary>输出 PNG 允许的最大文件大小,单位为字节(50 MiB)。</summary>
public const long MaximumFileSizeBytes = 50L * 1024L * 1024L;
/// <summary>写入 PNG 的物理分辨率元数据,单位 DPI。</summary>
public const float OutputDpi = 300f;
private const int MaximumFilenameAttempts = 1024;
/// <summary>
/// 在开关开启时导出规划地图 PNG。
///
/// 参数:enabled 为导出开关;request 包含不可变地图和输出根目录。
/// 返回:开关关闭时返回 Skipped;成功时返回 Saved、路径、字节数与像素尺寸;输入或尺寸不合法时返回失败信息。
/// 注意:该方法仅用于调试,绝不会影响地图创建和缓存结果。
/// </summary>
public static PlanningMapImageExportResult ExportIfEnabled(bool enabled, PlanningMapImageExportRequest request)
{
if (!enabled) return new PlanningMapImageExportResult { Skipped = true, Message = "Planning map image export is disabled." };
if (request == null || request.Map == null) return Rejected("Planning map image export requires a PlanningGridMap.");
if (string.IsNullOrWhiteSpace(request.OutputRootDirectory)) return Rejected("Planning map image export requires an output root directory.");
long width = (long)request.Map.Cols * PixelsPerCell, height = (long)request.Map.Rows * PixelsPerCell;
if (width <= 0 || height <= 0 || width > MaximumImageEdgePixels || height > MaximumImageEdgePixels) return Rejected("Planning map image dimensions exceed the permitted edge.", width, height);
string temporary = null;
try
{
string directory = Path.Combine(request.OutputRootDirectory, "PlanningMapExports");
Directory.CreateDirectory(directory);
using (FileStream stream = CreateTemporaryFile(directory, out string finalPath, out temporary))
{
byte[] rgba = PlanningMapImageRenderer.Render(request.Map, PixelsPerCell, out int pixelWidth, out int pixelHeight);
ValidatedPngWriter.Write(rgba, pixelWidth, pixelHeight, stream);
}
long length = new FileInfo(temporary).Length;
if (length > MaximumFileSizeBytes) return Rejected("Planning map image PNG exceeds 50 MiB.", width, height);
string completed = temporary.Substring(0, temporary.Length - 4);
File.Move(temporary, completed); temporary = null;
return new PlanningMapImageExportResult { Saved = true, FilePath = completed, FileSizeBytes = length, PixelWidth = (int)width, PixelHeight = (int)height, Message = "Planning map image export saved." };
}
catch (Exception exception) { return Rejected("Planning map image export failed: " + exception.Message, width, height); }
finally { if (temporary != null && File.Exists(temporary)) File.Delete(temporary); }
}
private static PlanningMapImageExportResult Rejected(string message, long width = 0, long height = 0) { return new PlanningMapImageExportResult { Message = message, PixelWidth = width > int.MaxValue ? int.MaxValue : (int)width, PixelHeight = height > int.MaxValue ? int.MaxValue : (int)height }; }
private static FileStream CreateTemporaryFile(string directory, out string finalPath, out string temporaryPath)
{
string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss_fff");
for (int index = 0; index < MaximumFilenameAttempts; index++)
{
string suffix = index == 0 ? string.Empty : "_" + index;
finalPath = Path.Combine(directory, "PlanningMap_" + timestamp + suffix + ".png"); temporaryPath = finalPath + ".tmp";
if (File.Exists(finalPath)) continue;
try { return new FileStream(temporaryPath, FileMode.CreateNew, FileAccess.Write, FileShare.None); }
catch (IOException) { }
}
finalPath = null; temporaryPath = null; throw new IOException("Could not reserve a unique PlanningMap PNG filename.");
}
}