360 lines
12 KiB
C#
360 lines
12 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
|
|
namespace PathLogic.Models
|
|
{
|
|
/// <summary>
|
|
/// 맵 데이터를 관리하는 클래스
|
|
/// 기존 AGV 맵 파일 형식과 호환
|
|
/// </summary>
|
|
public class MapData
|
|
{
|
|
/// <summary>
|
|
/// 맵의 모든 노드 목록
|
|
/// </summary>
|
|
public List<MapNode> Nodes { get; set; } = new List<MapNode>();
|
|
|
|
/// <summary>
|
|
/// 맵 생성 일자
|
|
/// </summary>
|
|
public DateTime CreatedDate { get; set; } = DateTime.Now;
|
|
|
|
/// <summary>
|
|
/// 맵 버전
|
|
/// </summary>
|
|
public string Version { get; set; } = "1.0";
|
|
|
|
/// <summary>
|
|
/// 기본 생성자
|
|
/// </summary>
|
|
public MapData()
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// 노드 ID로 노드 찾기
|
|
/// </summary>
|
|
/// <param name="nodeId">노드 ID</param>
|
|
/// <returns>해당 노드, 없으면 null</returns>
|
|
public MapNode GetNodeById(string nodeId)
|
|
{
|
|
return Nodes.FirstOrDefault(n => n.NodeId == nodeId);
|
|
}
|
|
|
|
/// <summary>
|
|
/// RFID ID로 노드 찾기
|
|
/// </summary>
|
|
/// <param name="rfidId">RFID ID</param>
|
|
/// <returns>해당 노드, 없으면 null</returns>
|
|
public MapNode GetNodeByRfidId(string rfidId)
|
|
{
|
|
return Nodes.FirstOrDefault(n => n.RfidId == rfidId);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 네비게이션 가능한 노드만 반환
|
|
/// </summary>
|
|
/// <returns>네비게이션 가능한 노드 목록</returns>
|
|
public List<MapNode> GetNavigationNodes()
|
|
{
|
|
return Nodes.Where(n => n.IsNavigationNode()).ToList();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 특정 타입의 노드들 반환
|
|
/// </summary>
|
|
/// <param name="nodeType">노드 타입</param>
|
|
/// <returns>해당 타입의 노드 목록</returns>
|
|
public List<MapNode> GetNodesByType(NodeType nodeType)
|
|
{
|
|
return Nodes.Where(n => n.Type == nodeType).ToList();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 도킹 스테이션 노드들 반환
|
|
/// </summary>
|
|
/// <returns>도킹 스테이션 노드 목록</returns>
|
|
public List<MapNode> GetDockingStations()
|
|
{
|
|
return Nodes.Where(n => n.Type == NodeType.Docking || n.Type == NodeType.Charging).ToList();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 충전 스테이션 노드들 반환
|
|
/// </summary>
|
|
/// <returns>충전 스테이션 노드 목록</returns>
|
|
public List<MapNode> GetChargingStations()
|
|
{
|
|
return Nodes.Where(n => n.Type == NodeType.Charging).ToList();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 회전 가능한 노드들 반환
|
|
/// </summary>
|
|
/// <returns>회전 가능한 노드 목록</returns>
|
|
public List<MapNode> GetRotationNodes()
|
|
{
|
|
return Nodes.Where(n => n.CanPerformRotation()).ToList();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 노드 추가
|
|
/// </summary>
|
|
/// <param name="node">추가할 노드</param>
|
|
/// <returns>추가 성공 여부</returns>
|
|
public bool AddNode(MapNode node)
|
|
{
|
|
if (node == null || GetNodeById(node.NodeId) != null)
|
|
return false;
|
|
|
|
Nodes.Add(node);
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 노드 제거
|
|
/// </summary>
|
|
/// <param name="nodeId">제거할 노드 ID</param>
|
|
/// <returns>제거 성공 여부</returns>
|
|
public bool RemoveNode(string nodeId)
|
|
{
|
|
var node = GetNodeById(nodeId);
|
|
if (node == null) return false;
|
|
|
|
// 다른 노드들의 연결에서도 제거
|
|
foreach (var otherNode in Nodes)
|
|
{
|
|
otherNode.ConnectedNodes.Remove(nodeId);
|
|
}
|
|
|
|
return Nodes.Remove(node);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 두 노드 간 연결 추가
|
|
/// </summary>
|
|
/// <param name="fromNodeId">시작 노드 ID</param>
|
|
/// <param name="toNodeId">도착 노드 ID</param>
|
|
/// <param name="bidirectional">양방향 연결 여부</param>
|
|
/// <returns>연결 성공 여부</returns>
|
|
public bool AddConnection(string fromNodeId, string toNodeId, bool bidirectional = true)
|
|
{
|
|
var fromNode = GetNodeById(fromNodeId);
|
|
var toNode = GetNodeById(toNodeId);
|
|
|
|
if (fromNode == null || toNode == null) return false;
|
|
|
|
// 단방향 연결
|
|
if (!fromNode.ConnectedNodes.Contains(toNodeId))
|
|
{
|
|
fromNode.ConnectedNodes.Add(toNodeId);
|
|
fromNode.ModifiedDate = DateTime.Now;
|
|
}
|
|
|
|
// 양방향 연결
|
|
if (bidirectional && !toNode.ConnectedNodes.Contains(fromNodeId))
|
|
{
|
|
toNode.ConnectedNodes.Add(fromNodeId);
|
|
toNode.ModifiedDate = DateTime.Now;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 두 노드 간 연결 제거
|
|
/// </summary>
|
|
/// <param name="fromNodeId">시작 노드 ID</param>
|
|
/// <param name="toNodeId">도착 노드 ID</param>
|
|
/// <param name="bidirectional">양방향 제거 여부</param>
|
|
/// <returns>제거 성공 여부</returns>
|
|
public bool RemoveConnection(string fromNodeId, string toNodeId, bool bidirectional = true)
|
|
{
|
|
var fromNode = GetNodeById(fromNodeId);
|
|
var toNode = GetNodeById(toNodeId);
|
|
|
|
if (fromNode == null || toNode == null) return false;
|
|
|
|
bool removed = false;
|
|
|
|
// 단방향 제거
|
|
if (fromNode.ConnectedNodes.Remove(toNodeId))
|
|
{
|
|
fromNode.ModifiedDate = DateTime.Now;
|
|
removed = true;
|
|
}
|
|
|
|
// 양방향 제거
|
|
if (bidirectional && toNode.ConnectedNodes.Remove(fromNodeId))
|
|
{
|
|
toNode.ModifiedDate = DateTime.Now;
|
|
removed = true;
|
|
}
|
|
|
|
return removed;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 두 노드가 연결되어 있는지 확인
|
|
/// </summary>
|
|
/// <param name="fromNodeId">시작 노드 ID</param>
|
|
/// <param name="toNodeId">도착 노드 ID</param>
|
|
/// <returns>연결 여부</returns>
|
|
public bool AreConnected(string fromNodeId, string toNodeId)
|
|
{
|
|
var fromNode = GetNodeById(fromNodeId);
|
|
return fromNode?.IsConnectedTo(toNodeId) ?? false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 특정 노드의 이웃 노드들 반환
|
|
/// </summary>
|
|
/// <param name="nodeId">노드 ID</param>
|
|
/// <returns>이웃 노드 목록</returns>
|
|
public List<MapNode> GetNeighbors(string nodeId)
|
|
{
|
|
var node = GetNodeById(nodeId);
|
|
if (node == null) return new List<MapNode>();
|
|
|
|
var neighbors = new List<MapNode>();
|
|
foreach (var connectedId in node.ConnectedNodes)
|
|
{
|
|
var neighbor = GetNodeById(connectedId);
|
|
if (neighbor != null && neighbor.IsNavigationNode())
|
|
{
|
|
neighbors.Add(neighbor);
|
|
}
|
|
}
|
|
|
|
return neighbors;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 맵 데이터 유효성 검증
|
|
/// </summary>
|
|
/// <returns>검증 결과 메시지</returns>
|
|
public List<string> ValidateMap()
|
|
{
|
|
var issues = new List<string>();
|
|
|
|
// 노드 ID 중복 검사
|
|
var nodeIds = Nodes.Select(n => n.NodeId).ToList();
|
|
var duplicateIds = nodeIds.GroupBy(id => id)
|
|
.Where(g => g.Count() > 1)
|
|
.Select(g => g.Key);
|
|
|
|
foreach (var duplicateId in duplicateIds)
|
|
{
|
|
issues.Add($"중복된 노드 ID: {duplicateId}");
|
|
}
|
|
|
|
// RFID ID 중복 검사
|
|
var rfidIds = Nodes.Where(n => n.HasRfid())
|
|
.Select(n => n.RfidId)
|
|
.ToList();
|
|
var duplicateRfids = rfidIds.GroupBy(id => id)
|
|
.Where(g => g.Count() > 1)
|
|
.Select(g => g.Key);
|
|
|
|
foreach (var duplicateRfid in duplicateRfids)
|
|
{
|
|
issues.Add($"중복된 RFID ID: {duplicateRfid}");
|
|
}
|
|
|
|
// 잘못된 연결 검사
|
|
foreach (var node in Nodes)
|
|
{
|
|
foreach (var connectedId in node.ConnectedNodes)
|
|
{
|
|
if (GetNodeById(connectedId) == null)
|
|
{
|
|
issues.Add($"노드 {node.NodeId}가 존재하지 않는 노드 {connectedId}에 연결됨");
|
|
}
|
|
}
|
|
}
|
|
|
|
// 고립된 네비게이션 노드 검사
|
|
var navigationNodes = GetNavigationNodes();
|
|
foreach (var node in navigationNodes)
|
|
{
|
|
if (node.ConnectedNodes.Count == 0)
|
|
{
|
|
issues.Add($"고립된 노드: {node.NodeId}");
|
|
}
|
|
}
|
|
|
|
return issues;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 맵 통계 정보 반환
|
|
/// </summary>
|
|
/// <returns>맵 통계</returns>
|
|
public MapStatistics GetStatistics()
|
|
{
|
|
var stats = new MapStatistics();
|
|
var navigationNodes = GetNavigationNodes();
|
|
|
|
stats.TotalNodes = Nodes.Count;
|
|
stats.NavigationNodes = navigationNodes.Count;
|
|
stats.DockingStations = GetNodesByType(NodeType.Docking).Count;
|
|
stats.ChargingStations = GetNodesByType(NodeType.Charging).Count;
|
|
stats.RotationNodes = GetRotationNodes().Count;
|
|
stats.LabelNodes = GetNodesByType(NodeType.Label).Count;
|
|
stats.ImageNodes = GetNodesByType(NodeType.Image).Count;
|
|
|
|
// 연결 수 계산
|
|
stats.TotalConnections = navigationNodes.Sum(n => n.ConnectedNodes.Count) / 2; // 양방향이므로 2로 나눔
|
|
|
|
// RFID 할당된 노드 수
|
|
stats.NodesWithRfid = Nodes.Count(n => n.HasRfid());
|
|
|
|
return stats;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 맵 데이터 복사
|
|
/// </summary>
|
|
/// <returns>복사된 맵 데이터</returns>
|
|
public MapData Clone()
|
|
{
|
|
var clonedMap = new MapData
|
|
{
|
|
CreatedDate = CreatedDate,
|
|
Version = Version
|
|
};
|
|
|
|
foreach (var node in Nodes)
|
|
{
|
|
clonedMap.Nodes.Add(node.Clone());
|
|
}
|
|
|
|
return clonedMap;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 맵 통계 정보 클래스
|
|
/// </summary>
|
|
public class MapStatistics
|
|
{
|
|
public int TotalNodes { get; set; }
|
|
public int NavigationNodes { get; set; }
|
|
public int DockingStations { get; set; }
|
|
public int ChargingStations { get; set; }
|
|
public int RotationNodes { get; set; }
|
|
public int LabelNodes { get; set; }
|
|
public int ImageNodes { get; set; }
|
|
public int TotalConnections { get; set; }
|
|
public int NodesWithRfid { get; set; }
|
|
|
|
public override string ToString()
|
|
{
|
|
return $"총 노드: {TotalNodes}, 네비게이션: {NavigationNodes}, " +
|
|
$"도킹: {DockingStations}, 충전: {ChargingStations}, " +
|
|
$"회전: {RotationNodes}, 연결: {TotalConnections}, " +
|
|
$"RFID 할당: {NodesWithRfid}";
|
|
}
|
|
}
|
|
} |