812 lines
31 KiB
C#
812 lines
31 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using Autodesk.Navisworks.Api;
|
||
using NavisworksTransport.Core.Models;
|
||
using NavisworksTransport.Utils;
|
||
|
||
namespace NavisworksTransport.Core
|
||
{
|
||
/// <summary>
|
||
/// 路径分析引擎
|
||
/// 负责计算路径的各项指标和评分
|
||
/// </summary>
|
||
public class PathAnalysisEngine
|
||
{
|
||
private readonly PathDatabase _database;
|
||
private const double HOTSPOT_RADIUS = 3.0; // 热点半径:3米
|
||
private const double ENDPOINT_THRESHOLD_METERS = 2.0; // 终点分组阈值:2米
|
||
private const double IDEAL_SPEED = 1.0; // 理想速度:1.0 m/s
|
||
|
||
/// <summary>
|
||
/// 获取终点分组阈值(转换为模型单位用于距离比较)
|
||
/// </summary>
|
||
private double EndpointThreshold => NavisworksTransport.Utils.UnitsConverter.ConvertFromMeters(ENDPOINT_THRESHOLD_METERS);
|
||
|
||
/// <summary>
|
||
/// 构造函数
|
||
/// </summary>
|
||
public PathAnalysisEngine(PathDatabase database)
|
||
{
|
||
_database = database ?? throw new ArgumentNullException(nameof(database));
|
||
}
|
||
|
||
#region 公共方法
|
||
|
||
/// <summary>
|
||
/// 分析单条路径的所有指标
|
||
/// </summary>
|
||
public PathDetailedAnalysis AnalyzePath(PathRoute route, AnalysisContext context)
|
||
{
|
||
if (route == null)
|
||
throw new ArgumentNullException(nameof(route));
|
||
|
||
var analysis = new PathDetailedAnalysis
|
||
{
|
||
RouteId = route.Id,
|
||
RouteName = route.Name,
|
||
TotalLength = route.TotalLength,
|
||
EstimatedTime = route.EstimatedTime,
|
||
Strategy = context?.Strategy ?? AnalysisStrategies.Balanced,
|
||
AnalysisTime = DateTime.Now
|
||
};
|
||
|
||
try
|
||
{
|
||
// 1. 获取碰撞数据
|
||
var collisions = GetCollisionsForRoute(route.Id);
|
||
analysis.CollisionCount = collisions?.Count ?? 0;
|
||
|
||
// 2. 检测碰撞热点
|
||
analysis.Hotspots = DetectHotspots(collisions, HOTSPOT_RADIUS);
|
||
analysis.HotspotCount = analysis.Hotspots.Count;
|
||
|
||
// 3. 计算各维度分数(四维度:安全、效率、转弯、直达)
|
||
analysis.SafetyScore = CalculateSafetyScore(analysis.CollisionCount, analysis.HotspotCount);
|
||
analysis.EfficiencyScore = CalculateEfficiencyScore(route, context?.GroupMinLength ?? route.TotalLength);
|
||
analysis.TurnDifficultyScore = CalculateTurnDifficultyScore(route.Edges);
|
||
analysis.TortuosityScore = CalculateTortuosityScore(route);
|
||
analysis.RedundancyScore = 0; // 已移除,保留字段兼容
|
||
|
||
// 4. 计算综合加权评分
|
||
analysis.WeightedScore = CalculateWeightedScore(analysis, analysis.Strategy);
|
||
|
||
LogManager.Info($"[路径分析] {route.Name}: 安全{analysis.SafetyScore:F0}, 效率{analysis.EfficiencyScore:F0}, " +
|
||
$"转弯{analysis.TurnDifficultyScore:F0}, 直达{analysis.TortuosityScore:F0}, 综合{analysis.WeightedScore:F0}");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"[路径分析] 分析路径 {route.Name} 失败: {ex.Message}", ex);
|
||
// 设置默认值,避免崩溃
|
||
SetDefaultScores(analysis);
|
||
}
|
||
|
||
return analysis;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 对多条路径进行分组(按终点)
|
||
/// </summary>
|
||
public List<EndpointGroup> GroupPathsByEndpoint(List<PathRoute> routes)
|
||
{
|
||
if (routes == null || routes.Count == 0)
|
||
return new List<EndpointGroup>();
|
||
|
||
var groups = new List<EndpointGroup>();
|
||
|
||
foreach (var route in routes)
|
||
{
|
||
var endPoint = route.GetEndPoint()?.Position;
|
||
if (endPoint == null)
|
||
{
|
||
LogManager.Warning($"[路径分组] 路径 {route.Name} 没有终点,跳过");
|
||
continue;
|
||
}
|
||
|
||
// 查找是否已有匹配的组(以终点为主,2米阈值)
|
||
var matchingGroup = groups.FirstOrDefault(g =>
|
||
Distance(g.EndPoint, endPoint) < EndpointThreshold);
|
||
|
||
if (matchingGroup != null)
|
||
{
|
||
// 添加到现有组
|
||
var analysis = AnalyzePath(route, new AnalysisContext { GroupMinTime = 0 });
|
||
matchingGroup.PathAnalyses.Add(analysis);
|
||
// 更新组中心点
|
||
matchingGroup.EndPoint = CalculateGroupCenter(matchingGroup.PathAnalyses, routes);
|
||
LogManager.Debug($"[路径分组] 路径 {route.Name} 加入现有组 {matchingGroup.GroupId}");
|
||
}
|
||
else
|
||
{
|
||
// 创建新组
|
||
var newGroup = new EndpointGroup
|
||
{
|
||
GroupId = Guid.NewGuid().ToString("N").Substring(0, 8),
|
||
GroupName = $"终点组-{groups.Count + 1}",
|
||
EndPoint = endPoint,
|
||
EndPointDescription = route.GetEndPoint()?.Name ?? "未知终点"
|
||
};
|
||
|
||
var analysis = AnalyzePath(route, new AnalysisContext { GroupMinTime = 0 });
|
||
newGroup.PathAnalyses.Add(analysis);
|
||
groups.Add(newGroup);
|
||
|
||
LogManager.Info($"[路径分组] 创建新组 {newGroup.GroupId},终点: {newGroup.EndPointDescription}");
|
||
}
|
||
}
|
||
|
||
// 更新各组的组内统计数据
|
||
foreach (var group in groups)
|
||
{
|
||
UpdateGroupStatistics(group);
|
||
}
|
||
|
||
LogManager.Info($"[路径分组] 完成,共 {groups.Count} 个组,{routes.Count} 条路径");
|
||
return groups;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 分析多条路径并分组,包含完整的上下文计算
|
||
/// </summary>
|
||
public List<EndpointGroup> AnalyzeAndGroupPaths(List<PathRoute> routes, string strategy)
|
||
{
|
||
if (routes == null || routes.Count == 0)
|
||
return new List<EndpointGroup>();
|
||
|
||
// 第一步:初步分析所有路径(获取基础数据)
|
||
var analyses = new List<PathDetailedAnalysis>();
|
||
foreach (var route in routes)
|
||
{
|
||
var analysis = AnalyzePath(route, new AnalysisContext { Strategy = strategy });
|
||
analyses.Add(analysis);
|
||
}
|
||
|
||
// 第二步:按终点分组
|
||
var groups = new List<EndpointGroup>();
|
||
var processed = new HashSet<string>();
|
||
|
||
LogManager.Info($"[路径分组] 开始对 {analyses.Count} 条分析结果进行分组");
|
||
|
||
foreach (var analysis in analyses)
|
||
{
|
||
if (processed.Contains(analysis.RouteId))
|
||
{
|
||
LogManager.Debug($"[路径分组] 跳过已处理: {analysis.RouteId}");
|
||
continue;
|
||
}
|
||
|
||
var route = routes.FirstOrDefault(r => r.Id == analysis.RouteId);
|
||
if (route == null)
|
||
{
|
||
LogManager.Warning($"[路径分组] 找不到路径: {analysis.RouteId}");
|
||
continue;
|
||
}
|
||
|
||
var endPointObj = route.GetEndPoint();
|
||
var endPoint = endPointObj?.Position;
|
||
if (endPoint == null)
|
||
{
|
||
LogManager.Warning($"[路径分组] 路径 {route.Name} 没有终点");
|
||
continue;
|
||
}
|
||
|
||
LogManager.Info($"[路径分组] 处理路径 {route.Name}, 终点: ({endPoint.X:F2}, {endPoint.Y:F2}, {endPoint.Z:F2})");
|
||
|
||
// 找到同组的所有路径(包括当前路径)
|
||
var groupAnalyses = new List<PathDetailedAnalysis>();
|
||
// 首先添加当前路径
|
||
groupAnalyses.Add(analysis);
|
||
processed.Add(analysis.RouteId);
|
||
|
||
foreach (var otherAnalysis in analyses)
|
||
{
|
||
if (processed.Contains(otherAnalysis.RouteId))
|
||
continue;
|
||
|
||
var otherRoute = routes.FirstOrDefault(r => r.Id == otherAnalysis.RouteId);
|
||
if (otherRoute == null)
|
||
continue;
|
||
|
||
var otherEndPoint = otherRoute.GetEndPoint()?.Position;
|
||
if (otherEndPoint == null)
|
||
continue;
|
||
|
||
if (Distance(endPoint, otherEndPoint) < EndpointThreshold)
|
||
{
|
||
groupAnalyses.Add(otherAnalysis);
|
||
processed.Add(otherAnalysis.RouteId);
|
||
}
|
||
}
|
||
|
||
if (groupAnalyses.Count > 0)
|
||
{
|
||
LogManager.Info($"[路径分组] 创建组,包含 {groupAnalyses.Count} 条路径");
|
||
|
||
try
|
||
{
|
||
var group = new EndpointGroup
|
||
{
|
||
GroupId = Guid.NewGuid().ToString("N").Substring(0, 8),
|
||
EndPoint = CalculateGroupCenter(groupAnalyses, routes),
|
||
PathAnalyses = groupAnalyses
|
||
};
|
||
|
||
// 设置组名称和终点描述
|
||
var firstRoute = routes.FirstOrDefault(r => r.Id == groupAnalyses.First().RouteId);
|
||
if (firstRoute != null)
|
||
{
|
||
var firstEndPoint = firstRoute.GetEndPoint();
|
||
group.GroupName = $"终点: {firstEndPoint?.Name ?? "未知"}";
|
||
group.EndPointDescription = $"坐标: ({group.EndPoint.X:F1}, {group.EndPoint.Y:F1}, {group.EndPoint.Z:F1})";
|
||
}
|
||
|
||
groups.Add(group);
|
||
LogManager.Info($"[路径分组] 组创建成功: {group.GroupName}");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"[路径分组] 创建组失败: {ex.Message}", ex);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
LogManager.Warning($"[路径分组] 组内没有路径");
|
||
}
|
||
}
|
||
|
||
// 第三步:更新效率分数(基于组内最短路径)和排名
|
||
foreach (var group in groups)
|
||
{
|
||
UpdateGroupStatistics(group);
|
||
|
||
// 只更新效率分数和综合评分,保留其他固有属性
|
||
foreach (var analysis in group.PathAnalyses)
|
||
{
|
||
var route = routes.FirstOrDefault(r => r.Id == analysis.RouteId);
|
||
if (route != null)
|
||
{
|
||
// 只重新计算效率(基于组内最短路径)
|
||
analysis.EfficiencyScore = CalculateEfficiencyScore(route, group.MinLength);
|
||
// 重新计算综合加权评分
|
||
analysis.WeightedScore = CalculateWeightedScore(analysis, strategy);
|
||
}
|
||
analysis.GroupId = group.GroupId;
|
||
}
|
||
|
||
// 排序并更新排名
|
||
var sortedAnalyses = group.PathAnalyses
|
||
.OrderByDescending(a => a.WeightedScore)
|
||
.ToList();
|
||
|
||
for (int i = 0; i < sortedAnalyses.Count; i++)
|
||
{
|
||
sortedAnalyses[i].GroupRanking = i + 1;
|
||
}
|
||
|
||
group.PathAnalyses = sortedAnalyses;
|
||
}
|
||
|
||
return groups;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检测碰撞热点
|
||
/// </summary>
|
||
public List<CollisionHotspot> DetectHotspots(List<CollisionResult> collisions, double radius = HOTSPOT_RADIUS)
|
||
{
|
||
var hotspots = new List<CollisionHotspot>();
|
||
|
||
if (collisions == null || collisions.Count < 2)
|
||
return hotspots;
|
||
|
||
var processed = new HashSet<int>();
|
||
|
||
for (int i = 0; i < collisions.Count; i++)
|
||
{
|
||
if (processed.Contains(i))
|
||
continue;
|
||
|
||
var center = collisions[i].Center;
|
||
if (center == null)
|
||
continue;
|
||
|
||
var nearbyCollisions = new List<CollisionResult> { collisions[i] };
|
||
|
||
// 查找范围内的其他碰撞
|
||
for (int j = i + 1; j < collisions.Count; j++)
|
||
{
|
||
if (processed.Contains(j))
|
||
continue;
|
||
|
||
var otherCenter = collisions[j].Center;
|
||
if (otherCenter == null)
|
||
continue;
|
||
|
||
if (Distance(center, otherCenter) <= radius)
|
||
{
|
||
nearbyCollisions.Add(collisions[j]);
|
||
processed.Add(j);
|
||
}
|
||
}
|
||
|
||
// 如果达到阈值(≥2次),创建热点
|
||
if (nearbyCollisions.Count >= 2)
|
||
{
|
||
var hotspot = new CollisionHotspot
|
||
{
|
||
Center = CalculateCenter(nearbyCollisions),
|
||
Radius = radius,
|
||
CollisionCount = nearbyCollisions.Count,
|
||
CollidedObjectNames = nearbyCollisions
|
||
.Select(c => GetSafeDisplayName(c.Item2))
|
||
.Distinct()
|
||
.ToList()
|
||
};
|
||
hotspots.Add(hotspot);
|
||
}
|
||
|
||
processed.Add(i);
|
||
}
|
||
|
||
LogManager.Debug($"[热点检测] 发现 {hotspots.Count} 个热点");
|
||
return hotspots.OrderByDescending(h => h.CollisionCount).ToList();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 计算加权综合评分
|
||
/// </summary>
|
||
public double CalculateWeightedScore(PathDetailedAnalysis analysis, string strategy)
|
||
{
|
||
var weights = AnalysisStrategies.GetWeights(strategy);
|
||
|
||
// 四维度加权计算:安全、效率、转弯、直达
|
||
double weightedScore =
|
||
analysis.SafetyScore * weights[0] +
|
||
analysis.EfficiencyScore * weights[1] +
|
||
analysis.TurnDifficultyScore * weights[2] +
|
||
analysis.TortuosityScore * weights[3];
|
||
|
||
return Math.Round(weightedScore, 1);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 查找组内最佳路径
|
||
/// </summary>
|
||
public PathDetailedAnalysis FindBestPathInGroup(EndpointGroup group, string strategy)
|
||
{
|
||
if (group?.PathAnalyses == null || group.PathAnalyses.Count == 0)
|
||
return null;
|
||
|
||
// 如果只有一条路径,直接返回
|
||
if (group.PathAnalyses.Count == 1)
|
||
return group.PathAnalyses[0];
|
||
|
||
// 重新计算加权评分
|
||
foreach (var analysis in group.PathAnalyses)
|
||
{
|
||
analysis.WeightedScore = CalculateWeightedScore(analysis, strategy);
|
||
}
|
||
|
||
// 按评分排序
|
||
var sortedPaths = group.PathAnalyses.OrderByDescending(a => a.WeightedScore).ToList();
|
||
|
||
// 如果前两名评分接近(差距<5分),优先选碰撞少的
|
||
if (sortedPaths.Count >= 2)
|
||
{
|
||
var first = sortedPaths[0];
|
||
var second = sortedPaths[1];
|
||
|
||
if (Math.Abs(first.WeightedScore - second.WeightedScore) < 5)
|
||
{
|
||
if (second.CollisionCount < first.CollisionCount)
|
||
{
|
||
LogManager.Info($"[最佳路径] 评分接近,选择碰撞更少的路径: {second.RouteName}");
|
||
return second;
|
||
}
|
||
}
|
||
}
|
||
|
||
return sortedPaths[0];
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成优化建议
|
||
/// </summary>
|
||
public List<CategorizedSuggestion> GenerateSuggestions(PathDetailedAnalysis analysis, EndpointGroup group)
|
||
{
|
||
var suggestions = new List<CategorizedSuggestion>();
|
||
|
||
// 1. 安全建议
|
||
if (analysis.CollisionCount > 0)
|
||
{
|
||
if (analysis.HotspotCount > 0)
|
||
{
|
||
suggestions.Add(new CategorizedSuggestion
|
||
{
|
||
Category = SuggestionCategory.Safety,
|
||
Title = $"发现 {analysis.CollisionCount} 次碰撞({analysis.HotspotCount} 个热点)",
|
||
Description = $"路径 {analysis.RouteName} 存在 {analysis.HotspotCount} 个碰撞热点区域," +
|
||
$"建议重点检查热点位置并优化路径避让",
|
||
RelatedRouteId = analysis.RouteId,
|
||
RelatedRouteName = analysis.RouteName,
|
||
Priority = analysis.HotspotCount >= 2 ? 5 : 4
|
||
});
|
||
}
|
||
else
|
||
{
|
||
suggestions.Add(new CategorizedSuggestion
|
||
{
|
||
Category = SuggestionCategory.Safety,
|
||
Title = $"发现 {analysis.CollisionCount} 次分散碰撞",
|
||
Description = $"路径 {analysis.RouteName} 有 {analysis.CollisionCount} 次碰撞," +
|
||
$"建议优化路径避让",
|
||
RelatedRouteId = analysis.RouteId,
|
||
RelatedRouteName = analysis.RouteName,
|
||
Priority = 3
|
||
});
|
||
}
|
||
}
|
||
else
|
||
{
|
||
suggestions.Add(new CategorizedSuggestion
|
||
{
|
||
Category = SuggestionCategory.Safety,
|
||
Title = "无碰撞,安全性良好",
|
||
Description = $"路径 {analysis.RouteName} 未检测到碰撞",
|
||
RelatedRouteId = analysis.RouteId,
|
||
RelatedRouteName = analysis.RouteName,
|
||
Priority = 1
|
||
});
|
||
}
|
||
|
||
// 2. 组内对比建议
|
||
if (group?.PathAnalyses?.Count > 1)
|
||
{
|
||
var lengthDiff = group.GetLengthDiffPercent(analysis.RouteId);
|
||
var timeDiff = group.GetTimeDiffPercent(analysis.RouteId);
|
||
|
||
if (lengthDiff > 30)
|
||
{
|
||
suggestions.Add(new CategorizedSuggestion
|
||
{
|
||
Category = SuggestionCategory.Efficiency,
|
||
Title = $"路径偏长(+{lengthDiff:F1}%)",
|
||
Description = $"该路径比组内最短路径长 {lengthDiff:F1}%," +
|
||
$"建议考虑更短路线",
|
||
RelatedRouteId = analysis.RouteId,
|
||
RelatedRouteName = analysis.RouteName,
|
||
Priority = 3
|
||
});
|
||
}
|
||
|
||
// 推荐最佳路径
|
||
if (analysis.IsBestInGroup)
|
||
{
|
||
suggestions.Add(new CategorizedSuggestion
|
||
{
|
||
Category = SuggestionCategory.GroupComparison,
|
||
Title = $"🏆 本组最佳路径推荐",
|
||
Description = $"路径 {analysis.RouteName} 是到达『{group.EndPointDescription}』的最佳选择," +
|
||
$"综合评分 {analysis.WeightedScore:F1} 分",
|
||
RelatedRouteId = analysis.RouteId,
|
||
RelatedRouteName = analysis.RouteName,
|
||
Priority = 5
|
||
});
|
||
}
|
||
}
|
||
|
||
// 3. 转弯建议
|
||
if (analysis.TurnDifficultyScore < 60)
|
||
{
|
||
suggestions.Add(new CategorizedSuggestion
|
||
{
|
||
Category = SuggestionCategory.TurnComplexity,
|
||
Title = "转弯难度较高",
|
||
Description = $"路径 {analysis.RouteName} 转弯难度分数较低," +
|
||
$"建议检查急转弯位置",
|
||
RelatedRouteId = analysis.RouteId,
|
||
RelatedRouteName = analysis.RouteName,
|
||
Priority = 3
|
||
});
|
||
}
|
||
|
||
return suggestions.OrderByDescending(s => s.Priority).ToList();
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 私有计算方法
|
||
|
||
/// <summary>
|
||
/// 计算安全性分数
|
||
/// </summary>
|
||
private double CalculateSafetyScore(int collisionCount, int hotspotCount)
|
||
{
|
||
// 简单扣分制:每个碰撞扣1分,每个热点扣1分
|
||
double penalty = collisionCount * 1.0 + hotspotCount * 1.0;
|
||
return Math.Max(0, 100 - penalty);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 计算效率分数 - 基于路径长度(越短越高效)
|
||
/// </summary>
|
||
public double CalculateEfficiencyScore(PathRoute route, double groupMinLength)
|
||
{
|
||
// 如果没有组内最短路径作为参考,使用当前路径长度
|
||
double referenceLength = groupMinLength > 0 ? groupMinLength : route.TotalLength;
|
||
|
||
if (referenceLength <= 0)
|
||
return 50; // 默认中等分数
|
||
|
||
// 计算与最短路径的差异率
|
||
double lengthDiffPercent = (route.TotalLength - referenceLength) / referenceLength;
|
||
|
||
// 长度效率分:相同为100分,每多10%扣10分
|
||
double efficiencyScore = Math.Max(0, 100 - lengthDiffPercent * 100);
|
||
|
||
return Math.Round(Math.Min(100, efficiencyScore), 1);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 计算转弯难度分数
|
||
/// </summary>
|
||
private double CalculateTurnDifficultyScore(List<PathEdge> edges)
|
||
{
|
||
if (edges == null || edges.Count == 0)
|
||
return 100; // 无转弯,满分
|
||
|
||
// 简单扣分制:每个圆弧转弯扣1分
|
||
int arcEdgeCount = edges.Count(e => e.SegmentType == PathSegmentType.Arc);
|
||
return Math.Max(0, 100 - arcEdgeCount * 1.0);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 计算路径曲折度分数
|
||
/// </summary>
|
||
private double CalculateTortuosityScore(PathRoute route)
|
||
{
|
||
var sortedPoints = route.GetSortedPoints();
|
||
if (sortedPoints.Count < 2)
|
||
return 100;
|
||
|
||
var startPoint = sortedPoints.First().Position;
|
||
var endPoint = sortedPoints.Last().Position;
|
||
|
||
// 计算直线距离(模型单位)
|
||
double straightDistanceModelUnits = Distance(startPoint, endPoint);
|
||
// 转换为米(与 TotalLength 单位一致)
|
||
double straightDistanceMeters = NavisworksTransport.Utils.UnitsConverter.ConvertToMeters(straightDistanceModelUnits);
|
||
|
||
if (straightDistanceMeters <= 0)
|
||
return 100;
|
||
|
||
// 直达性 = 直线距离 / 实际路径长度 × 100(越接近100越好)
|
||
// 实际路径长度由 TotalLength 计算属性提供(已转换为米)
|
||
if (route.TotalLength <= 0.001)
|
||
return 100;
|
||
|
||
double directness = straightDistanceMeters / route.TotalLength * 100;
|
||
double score = Math.Min(100, Math.Max(0, Math.Round(directness, 1)));
|
||
|
||
LogManager.Info($"[直达性计算] {route.Name}: 直线距离={straightDistanceMeters:F2}m, 实际长度={route.TotalLength:F2}m, 直达性={score:F1}");
|
||
|
||
return score;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 计算冗余度分数
|
||
/// </summary>
|
||
private double CalculateRedundancyScore(PathRoute route, List<ChannelInfo> channels)
|
||
{
|
||
// 简单扣分制:基于通道窄宽缩减量扣分
|
||
// 每缩减10%扣1分
|
||
|
||
// 如果路径没有物体参数,使用配置默认值
|
||
double objectWidth = route.MaxObjectWidth > 0 ? route.MaxObjectWidth : 1.0;
|
||
double safetyMargin = route.SafetyMargin > 0 ? route.SafetyMargin : 0.1;
|
||
double effectiveObjectWidth = objectWidth + safetyMargin * 2;
|
||
|
||
double channelWidth;
|
||
if (channels == null || channels.Count == 0)
|
||
{
|
||
var config = NavisworksTransport.Core.Config.ConfigManager.Instance.Current.Logistics;
|
||
channelWidth = config.WidthLimitMeters > 0 ? config.WidthLimitMeters : 3.0;
|
||
}
|
||
else
|
||
{
|
||
channelWidth = channels.Min(c => c.WidthLimit);
|
||
}
|
||
|
||
// 计算宽度缩减率(正值表示有冗余,负值表示不足)
|
||
double widthMargin = (channelWidth - effectiveObjectWidth) / effectiveObjectWidth;
|
||
|
||
// 每缩减10%扣1分(反向计算:越宽越高分)
|
||
double score = 100 - widthMargin * 100 * 0.1;
|
||
return Math.Min(100, Math.Max(0, score));
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 辅助方法
|
||
|
||
/// <summary>
|
||
/// 获取路径的碰撞数据
|
||
/// </summary>
|
||
private List<CollisionResult> GetCollisionsForRoute(string routeId)
|
||
{
|
||
try
|
||
{
|
||
// 获取最新的ClashDetective结果
|
||
var clashResult = _database.GetLatestClashDetectiveResultByRouteId(routeId);
|
||
if (clashResult == null)
|
||
return new List<CollisionResult>();
|
||
|
||
// 获取碰撞对象
|
||
var collisionObjects = _database.GetClashDetectiveCollisionObjects(clashResult.Id);
|
||
|
||
// 转换为CollisionResult列表
|
||
var collisions = new List<CollisionResult>();
|
||
foreach (var obj in collisionObjects)
|
||
{
|
||
if (obj.Item1PosX.HasValue && obj.Item1PosY.HasValue && obj.Item1PosZ.HasValue)
|
||
{
|
||
collisions.Add(new CollisionResult
|
||
{
|
||
Center = new Point3D(
|
||
obj.Item1PosX.Value,
|
||
obj.Item1PosY.Value,
|
||
obj.Item1PosZ.Value),
|
||
Item2 = ModelItemProxyHelper.CreateProxy(obj.DisplayName)
|
||
});
|
||
}
|
||
}
|
||
|
||
return collisions;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"[获取碰撞数据] 路径 {routeId} 失败: {ex.Message}");
|
||
return new List<CollisionResult>();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 计算两点距离
|
||
/// </summary>
|
||
private double Distance(Point3D p1, Point3D p2)
|
||
{
|
||
if (p1 == null || p2 == null)
|
||
return double.MaxValue;
|
||
|
||
double dx = p1.X - p2.X;
|
||
double dy = p1.Y - p2.Y;
|
||
double dz = p1.Z - p2.Z;
|
||
|
||
return Math.Sqrt(dx * dx + dy * dy + dz * dz);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 计算碰撞中心点
|
||
/// </summary>
|
||
private Point3D CalculateCenter(List<CollisionResult> collisions)
|
||
{
|
||
if (collisions == null || collisions.Count == 0)
|
||
return new Point3D(0, 0, 0);
|
||
|
||
double sumX = 0, sumY = 0, sumZ = 0;
|
||
int count = 0;
|
||
|
||
foreach (var collision in collisions)
|
||
{
|
||
if (collision.Center != null)
|
||
{
|
||
sumX += collision.Center.X;
|
||
sumY += collision.Center.Y;
|
||
sumZ += collision.Center.Z;
|
||
count++;
|
||
}
|
||
}
|
||
|
||
if (count == 0)
|
||
return new Point3D(0, 0, 0);
|
||
|
||
return new Point3D(sumX / count, sumY / count, sumZ / count);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 计算组中心点(取所有路径终点的平均值)
|
||
/// </summary>
|
||
private Point3D CalculateGroupCenter(List<PathDetailedAnalysis> analyses, List<PathRoute> routes)
|
||
{
|
||
if (analyses == null || analyses.Count == 0 || routes == null)
|
||
return new Point3D(0, 0, 0);
|
||
|
||
double sumX = 0, sumY = 0, sumZ = 0;
|
||
int count = 0;
|
||
|
||
foreach (var analysis in analyses)
|
||
{
|
||
var route = routes.FirstOrDefault(r => r.Id == analysis.RouteId);
|
||
var endPoint = route?.GetEndPoint()?.Position;
|
||
if (endPoint != null)
|
||
{
|
||
sumX += endPoint.X;
|
||
sumY += endPoint.Y;
|
||
sumZ += endPoint.Z;
|
||
count++;
|
||
}
|
||
}
|
||
|
||
if (count == 0)
|
||
return new Point3D(0, 0, 0);
|
||
|
||
return new Point3D(sumX / count, sumY / count, sumZ / count);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 更新组内统计信息
|
||
/// </summary>
|
||
private void UpdateGroupStatistics(EndpointGroup group)
|
||
{
|
||
if (group?.PathAnalyses == null || group.PathAnalyses.Count == 0)
|
||
return;
|
||
|
||
// 更新排名
|
||
var sorted = group.PathAnalyses.OrderByDescending(a => a.WeightedScore).ToList();
|
||
for (int i = 0; i < sorted.Count; i++)
|
||
{
|
||
sorted[i].GroupRanking = i + 1;
|
||
}
|
||
|
||
LogManager.Debug($"[组统计] 组 {group.GroupId} 共 {group.RouteCount} 条路径," +
|
||
$"最佳: {group.BestPath?.RouteName}");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 设置默认分数(错误恢复)
|
||
/// </summary>
|
||
private void SetDefaultScores(PathDetailedAnalysis analysis)
|
||
{
|
||
analysis.SafetyScore = 50;
|
||
analysis.EfficiencyScore = 50;
|
||
analysis.TurnDifficultyScore = 50;
|
||
analysis.TortuosityScore = 50;
|
||
analysis.RedundancyScore = 50;
|
||
analysis.WeightedScore = 50;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取安全的显示名称
|
||
/// </summary>
|
||
private string GetSafeDisplayName(ModelItem item)
|
||
{
|
||
if (item == null)
|
||
return "未知对象";
|
||
|
||
try
|
||
{
|
||
return item.DisplayName ?? "未命名对象";
|
||
}
|
||
catch
|
||
{
|
||
return "未知对象";
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
}
|
||
|
||
/// <summary>
|
||
/// ModelItem代理类(用于当没有实际ModelItem时)
|
||
/// </summary>
|
||
internal static class ModelItemProxyHelper
|
||
{
|
||
public static ModelItem CreateProxy(string displayName)
|
||
{
|
||
// 这是一个简化实现,实际可能需要更复杂的处理
|
||
// 这里仅用于存储显示名称
|
||
return null;
|
||
}
|
||
}
|
||
}
|