NavisworksTransport/src/PathPlanning/PathOptimizer.cs

524 lines
22 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Collections.Generic;
using System.Linq;
using Autodesk.Navisworks.Api;
namespace NavisworksTransport.PathPlanning
{
/// <summary>
/// 路径优化器
/// 提供多种路径优化算法,包括简化、平滑、避障等
/// </summary>
public class PathOptimizer
{
/// <summary>
/// 共线检测容差(米)
/// </summary>
private const double COLLINEAR_TOLERANCE_METERS = 0.1;
/// <summary>
/// 重复点检测容差(米)- 用于检测坐标完全相同的点
/// </summary>
private const double DUPLICATE_POINT_TOLERANCE_METERS = 0.001;
/// <summary>
/// 路径优化配置参数
/// </summary>
public class OptimizationConfig
{
/// <summary>
/// 是否启用路径简化(去除共线点)
/// </summary>
public bool EnableSimplification { get; set; } = true;
/// <summary>
/// 共线检测的容差值(米)
/// </summary>
public double CollinearTolerance { get; set; }
/// <summary>
/// 是否启用路径平滑(未来扩展)
/// </summary>
public bool EnableSmoothing { get; set; }
/// <summary>
/// 转弯半径(米,未来扩展)
/// </summary>
public double TurningRadius { get; set; }
/// <summary>
/// 圆弧分段数(未来扩展)
/// </summary>
public int ArcSegments { get; set; }
/// <summary>
/// 是否启用碰撞检测验证(未来扩展)
/// </summary>
public bool EnableCollisionCheck { get; set; }
/// <summary>
/// 物体长度(米,未来扩展)
/// </summary>
public double ObjectLength { get; set; }
/// <summary>
/// 物体宽度(米,未来扩展)
/// </summary>
public double ObjectWidth { get; set; }
}
private readonly OptimizationConfig _config;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="config">优化配置为null时使用默认配置</param>
public PathOptimizer(OptimizationConfig config = null)
{
_config = config ?? new OptimizationConfig();
}
/// <summary>
/// 优化路径的主入口
/// </summary>
/// <param name="originalPath">原始路径</param>
/// <param name="gridMap">网格地图(用于网格坐标转换)</param>
/// <returns>优化后的路径</returns>
public PathRoute OptimizePath(PathRoute originalPath, GridMap gridMap = null)
{
if (originalPath == null)
{
LogManager.Warning("[路径优化] 原始路径为null");
return null;
}
if (originalPath.Points.Count <= 2)
{
LogManager.Info("[路径优化] 路径点数量≤2无需优化");
return originalPath;
}
try
{
var startTime = DateTime.Now;
var originalCount = originalPath.Points.Count;
LogManager.Info($"[路径优化] 开始优化路径:{originalPath.Name},原始点数:{originalCount}");
// 复制路径点进行优化
var optimizedPoints = new List<PathPoint>(originalPath.Points);
// 1. 基础简化 - 使用基于网格的算法
if (_config.EnableSimplification)
{
if (gridMap != null)
{
optimizedPoints = SimplifyGridBasedPath(optimizedPoints, gridMap);
LogManager.Info($"[路径优化] 简化完成,点数:{originalCount} -> {optimizedPoints.Count}");
}
else
{
optimizedPoints = SimplifyCollinearPoints(optimizedPoints);
LogManager.Info($"[路径优化] 简化完成,点数:{originalCount} -> {optimizedPoints.Count}");
}
}
// 2. 未来扩展:路径平滑
if (_config.EnableSmoothing)
{
LogManager.Info("[路径优化] 路径平滑功能尚未实现");
// optimizedPoints = SmoothPath(optimizedPoints);
}
// 创建优化后的路径
var optimizedRoute = CreateOptimizedRoute(originalPath, optimizedPoints);
var duration = DateTime.Now - startTime;
LogManager.Info($"[路径优化] 优化完成,耗时:{duration.TotalMilliseconds:F1}ms优化率{(1.0 - (double)optimizedPoints.Count / originalCount) * 100:F1}%");
return optimizedRoute;
}
catch (Exception ex)
{
LogManager.Error($"[路径优化] 优化过程发生异常:{ex.Message}", ex);
return originalPath; // 发生异常时返回原始路径
}
}
/// <summary>
/// 基于网格坐标的路径简化算法
/// </summary>
/// <param name="points">原始路径点列表</param>
/// <param name="gridMap">网格地图</param>
/// <returns>简化后的路径点列表</returns>
private List<PathPoint> SimplifyGridBasedPath(List<PathPoint> points, GridMap gridMap)
{
if (points == null || points.Count < 3)
return points;
// 转换为网格坐标
var worldPath = points.Select(p => p.Position).ToList();
var gridPath = worldPath.Select(p => gridMap.WorldToGrid(p)).ToList();
// 步骤1先去除重复点但保护限速边界和多层高度
var dedupedGridPath = new List<GridPoint2D>();
var dedupedPoints = new List<PathPoint>();
for (int i = 0; i < gridPath.Count; i++)
{
// 🔥 关键修复:即使网格坐标相同,如果限速不同或高度不同也不能去重
// 2D场景hasHeightChange为false不影响原有逻辑
// 3D场景保留同网格不同高度的点
bool hasGridChange = i == 0 || !gridPath[i].Equals(gridPath[i-1]);
bool hasSpeedChange = i > 0 && HasSpeedLimitChange(points[i-1], points[i]);
bool hasHeightChange = i > 0 && Math.Abs(points[i].Position.Z - points[i-1].Position.Z) > 1e-6;
bool shouldKeep = hasGridChange || hasSpeedChange || hasHeightChange;
if (shouldKeep)
{
dedupedGridPath.Add(gridPath[i]);
dedupedPoints.Add(points[i]);
}
}
LogManager.Info($"[路径优化] 去重完成:{gridPath.Count} -> {dedupedPoints.Count} 个点");
// 步骤2基于去重后的路径进行方向优化
// 🔥 关键修复:不使用网格坐标,直接使用索引来跟踪保留的点
// 这样可以避免同一网格坐标的不同高度点被混淆
var simplifiedIndices = new List<int> { 0 }; // 保留起点索引
// 获取初始方向(归一化)
int initialDx = dedupedGridPath[1].X - dedupedGridPath[0].X;
int initialDy = dedupedGridPath[1].Y - dedupedGridPath[0].Y;
GridPoint2D prevDirection = new GridPoint2D(
initialDx == 0 ? 0 : Math.Sign(initialDx),
initialDy == 0 ? 0 : Math.Sign(initialDy)
);
// 简化路径:只保留转弯点、高度变化点和限速变化点
bool forceKeepNext = false;
for (int i = 2; i < dedupedGridPath.Count; i++)
{
// 计算当前线段的位移
int dx = dedupedGridPath[i].X - dedupedGridPath[i-1].X;
int dy = dedupedGridPath[i].Y - dedupedGridPath[i-1].Y;
// 归一化为单位方向向量 (-1, 0, 1)
var currentDirection = new GridPoint2D(
dx == 0 ? 0 : Math.Sign(dx),
dy == 0 ? 0 : Math.Sign(dy)
);
// 检查高度变化2D场景下始终为false不影响原有逻辑
bool hasHeightChange = HasHeightChange(
dedupedPoints[i-2],
dedupedPoints[i-1],
dedupedPoints[i]
);
// 检查限速变化
bool hasSpeedLimitChange = HasSpeedLimitChange(
dedupedPoints[i-2],
dedupedPoints[i-1],
dedupedPoints[i]
);
// 保留转折点、高度变化点或限速变化点
bool shouldKeep = !currentDirection.Equals(prevDirection) || hasHeightChange || hasSpeedLimitChange || forceKeepNext;
if (shouldKeep)
{
simplifiedIndices.Add(i - 1); // 保留索引而不是网格坐标
prevDirection = currentDirection;
}
// 🔥 如果当前检测到高度变化,下一个点也必须保留
forceKeepNext = hasHeightChange;
}
// 添加终点索引
simplifiedIndices.Add(dedupedGridPath.Count - 1);
LogManager.Info($"[网格路径优化] 简化完成:{dedupedGridPath.Count} -> {simplifiedIndices.Count} 个点");
// 步骤3根据索引提取PathPoint
var simplifiedPoints = new List<PathPoint>();
foreach (int index in simplifiedIndices)
{
simplifiedPoints.Add(dedupedPoints[index]);
}
// 打印优化后的路径点详情
LogManager.Info($"[优化后路径详情] 共 {simplifiedPoints.Count} 个点:");
for (int i = 0; i < simplifiedPoints.Count; i++)
{
var pt = simplifiedPoints[i];
LogManager.Debug($" [优化后点{i}] 位置=({pt.Position.X:F2}, {pt.Position.Y:F2}, Z={pt.Position.Z:F2}), 限速={pt.SpeedLimit:F1}");
}
return simplifiedPoints;
}
/// <summary>
/// 简化共线点(去除直线上的冗余点)
/// </summary>
/// <param name="points">原始路径点列表</param>
/// <returns>简化后的路径点列表</returns>
private List<PathPoint> SimplifyCollinearPoints(List<PathPoint> points)
{
if (points.Count <= 2) return points;
var simplified = new List<PathPoint>();
simplified.Add(points[0]); // 添加起点
int removedCount = 0;
// 遍历中间点,只保留转折点和限速边界点
for (int i = 1; i < points.Count - 1; i++)
{
var prevPoint = points[i - 1];
var currPoint = points[i];
var nextPoint = points[i + 1];
// 检查是否在同一直线上
bool isCollinear = IsCollinear(prevPoint.Position, currPoint.Position, nextPoint.Position);
// 🔥 关键修复:检查限速变化,即使共线也不能移除限速边界点
bool hasSpeedLimitChange = HasSpeedLimitChange(prevPoint, currPoint, nextPoint);
if (!isCollinear || hasSpeedLimitChange)
{
// 这是一个转折点或限速边界点,需要保留
simplified.Add(currPoint);
if (hasSpeedLimitChange)
{
LogManager.Debug($"[共线简化] 保留限速边界点 {i}: {prevPoint.SpeedLimit} -> {currPoint.SpeedLimit} -> {nextPoint.SpeedLimit}");
}
}
else
{
// 这是直线上的冗余点且无限速变化,跳过
removedCount++;
}
}
simplified.Add(points[points.Count - 1]); // 添加终点
LogManager.Info($"[共线简化] 简化完成,移除了 {removedCount} 个冗余点");
return simplified;
}
/// <summary>
/// 检查两个路径点之间是否有显著的高度变化
/// 用于路径优化时判断是否需要保留中间点
/// </summary>
/// <param name="point1">第一个路径点</param>
/// <param name="point2">第二个路径点</param>
/// <returns>如果有高度变化返回true需要保留中间点</returns>
private bool HasHeightChange(PathPoint point1, PathPoint point2)
{
const double heightTolerance = 1e-6; // 仅覆盖浮点数精度误差
return Math.Abs(point1.Position.Z - point2.Position.Z) > heightTolerance;
}
/// <summary>
/// 检查三个点之间是否有高度变化(用于判断是否可以跳过中间点)
/// </summary>
/// <param name="p1">第一个点的3D坐标</param>
/// <param name="p2">中间点的3D坐标</param>
/// <param name="p3">第三个点的3D坐标</param>
/// <returns>如果有高度变化返回true不能跳过中间点</returns>
private bool HasHeightChange(Point3D p1, Point3D p2, Point3D p3)
{
const double heightTolerance = 1e-6; // 仅覆盖浮点数精度误差
// 检查任意两点间是否有高度变化
if (Math.Abs(p1.Z - p2.Z) > heightTolerance ||
Math.Abs(p2.Z - p3.Z) > heightTolerance)
{
return true;
}
return false;
}
/// <summary>
/// 检查三个点之间是否有高度变化PathPoint版本
/// </summary>
/// <param name="point1">第一个路径点</param>
/// <param name="point2">中间路径点</param>
/// <param name="point3">第三个路径点</param>
/// <returns>如果有高度变化返回true不能跳过中间点</returns>
private bool HasHeightChange(PathPoint point1, PathPoint point2, PathPoint point3)
{
return HasHeightChange(point1.Position, point2.Position, point3.Position);
}
/// <summary>
/// 检查两个路径点之间是否有限速变化
/// 用于路径优化时判断是否需要保留中间点
/// </summary>
/// <param name="point1">第一个路径点</param>
/// <param name="point2">第二个路径点</param>
/// <returns>如果有限速变化返回true需要保留中间点</returns>
private bool HasSpeedLimitChange(PathPoint point1, PathPoint point2)
{
const double speedTolerance = 1e-6; // 仅覆盖浮点数精度误差
return Math.Abs(point1.SpeedLimit - point2.SpeedLimit) > speedTolerance;
}
/// <summary>
/// 检查三个点之间是否有限速变化(用于判断是否可以跳过中间点)
/// </summary>
/// <param name="point1">第一个路径点</param>
/// <param name="point2">中间路径点</param>
/// <param name="point3">第三个路径点</param>
/// <returns>如果有限速变化返回true不能跳过中间点</returns>
private bool HasSpeedLimitChange(PathPoint point1, PathPoint point2, PathPoint point3)
{
const double speedTolerance = 1e-6; // 仅覆盖浮点数精度误差
// 检查任意两点间是否有限速变化
if (Math.Abs(point1.SpeedLimit - point2.SpeedLimit) > speedTolerance ||
Math.Abs(point2.SpeedLimit - point3.SpeedLimit) > speedTolerance)
{
return true;
}
return false;
}
/// <summary>
/// 检查三点是否真正共线只有当三点都在同一条直线上且方向一致时才返回true
/// 🔥 关键修复:严格检查两个线段的方向是否一致,防止在转折点处错误合并
/// </summary>
/// <param name="p1">第一个点</param>
/// <param name="p2">第二个点(中间点)</param>
/// <param name="p3">第三个点</param>
/// <returns>如果三点在同一直线上且方向一致返回true</returns>
private bool IsCollinear(Point3D p1, Point3D p2, Point3D p3)
{
const double tolerance = COLLINEAR_TOLERANCE_METERS; // 容差值,单位:模型单位
// 🔥 修复:使用统一的高度检查函数
if (HasHeightChange(p1, p2, p3))
{
LogManager.Debug($"[共线检测] ❌ 拒绝Z方向变化({p1.X:F3},{p1.Y:F3},{p1.Z:F3}) -> ({p2.X:F3},{p2.Y:F3},{p2.Z:F3}) -> ({p3.X:F3},{p3.Y:F3},{p3.Z:F3})");
return false;
}
// 🔥 关键修复:分别检查两个线段的方向
// 线段1: p1 -> p2
double dx12 = p2.X - p1.X;
double dy12 = p2.Y - p1.Y;
// 线段2: p2 -> p3
double dx23 = p3.X - p2.X;
double dy23 = p3.Y - p2.Y;
// 🔥 修复:使用更严格的容差检查真正的重复点(坐标完全相同)
const double duplicatePointTolerance = DUPLICATE_POINT_TOLERANCE_METERS; // 用很小的值检查真正的重复点
bool isSegment1Zero = Math.Abs(dx12) < duplicatePointTolerance && Math.Abs(dy12) < duplicatePointTolerance;
bool isSegment2Zero = Math.Abs(dx23) < duplicatePointTolerance && Math.Abs(dy23) < duplicatePointTolerance;
if (isSegment1Zero || isSegment2Zero)
{
// 🔥 在真正重复点时才认为共线
return true;
}
// 🔥 关键修复:严格检查两个线段是否都是水平或都是垂直
bool isSegment1Horizontal = Math.Abs(dy12) < tolerance && Math.Abs(dx12) > tolerance;
bool isSegment1Vertical = Math.Abs(dx12) < tolerance && Math.Abs(dy12) > tolerance;
bool isSegment2Horizontal = Math.Abs(dy23) < tolerance && Math.Abs(dx23) > tolerance;
bool isSegment2Vertical = Math.Abs(dx23) < tolerance && Math.Abs(dy23) > tolerance;
// 🔥 核心逻辑:两个线段必须都是同一种类型(都水平或都垂直)
if (isSegment1Horizontal && isSegment2Horizontal)
{
// 检查水平方向是否一致(同向或反向都可以)
return (dx12 * dx23 > 0) || Math.Abs(dx12) < tolerance || Math.Abs(dx23) < tolerance;
}
else if (isSegment1Vertical && isSegment2Vertical)
{
// 检查垂直方向是否一致(同向或反向都可以)
return (dy12 * dy23 > 0) || Math.Abs(dy12) < tolerance || Math.Abs(dy23) < tolerance;
}
else
{
// 🔥 关键:不同类型的线段(一个水平一个垂直,或包含斜线)一律拒绝
string seg1Type = isSegment1Horizontal ? "水平" : (isSegment1Vertical ? "垂直" : "斜线");
string seg2Type = isSegment2Horizontal ? "水平" : (isSegment2Vertical ? "垂直" : "斜线");
return false;
}
}
/// <summary>
/// 创建优化后的路径对象
/// </summary>
/// <param name="originalPath">原始路径</param>
/// <param name="optimizedPoints">优化后的路径点</param>
/// <returns>优化后的路径对象</returns>
private PathRoute CreateOptimizedRoute(PathRoute originalPath, List<PathPoint> optimizedPoints)
{
var optimizedRoute = new PathRoute($"{originalPath.Name}_优化")
{
Id = originalPath.Id,
IsComplete = originalPath.IsComplete,
OriginalEndPoint = originalPath.OriginalEndPoint,
ActualEndPoint = originalPath.ActualEndPoint,
CompletionPercentage = originalPath.CompletionPercentage
};
// 重新分配索引和更新点类型
for (int i = 0; i < optimizedPoints.Count; i++)
{
var point = optimizedPoints[i];
point.Index = i;
// 重新确定点类型
if (i == 0)
point.Type = PathPointType.StartPoint;
else if (i == optimizedPoints.Count - 1)
point.Type = PathPointType.EndPoint;
else
point.Type = PathPointType.WayPoint;
optimizedRoute.Points.Add(point);
}
// 重新计算路径数据(但不保存到数据库,由调用方决定)
optimizedRoute.RecalculateRoute("路径优化");
return optimizedRoute;
}
#region
/// <summary>
/// 路径平滑处理(未来实现)
/// </summary>
/// <param name="points">路径点列表</param>
/// <returns>平滑后的路径点列表</returns>
private List<PathPoint> SmoothPath(List<PathPoint> points)
{
// TODO: 实现贝塞尔曲线或圆弧平滑
LogManager.Info("[路径平滑] 功能尚未实现");
return points;
}
/// <summary>
/// 在转角处添加圆弧(未来实现)
/// </summary>
/// <param name="points">路径点列表</param>
/// <returns>添加圆弧后的路径点列表</returns>
private List<PathPoint> AddTurningArcs(List<PathPoint> points)
{
// TODO: 在转角处添加圆弧
LogManager.Info("[圆弧转弯] 功能尚未实现");
return points;
}
#endregion
}
}