371 lines
16 KiB
C#
371 lines
16 KiB
C#
using RobotNet10.FleetManager.Shared.DTOs.RobotModel;
|
|
using RobotNet10.MapEditor.Shared.DTOs.Edge;
|
|
using RobotNet10.MapEditor.Shared.DTOs.Node;
|
|
using RobotNet10.MapManager.Data;
|
|
using RobotNet10.MapManager.Services;
|
|
|
|
namespace RobotNet10.FleetManager.Services;
|
|
|
|
/// <summary>
|
|
/// Service implementation for managing robot model map data based on VehicleType filtering
|
|
/// </summary>
|
|
public class RobotModelMapService(
|
|
IRobotModelService robotModelService,
|
|
IVehicleTypeService vehicleTypeService,
|
|
IMapQueryService mapQueryService,
|
|
ILayoutService layoutService,
|
|
Logger<RobotModelMapService> logger) : IRobotModelMapService
|
|
{
|
|
private readonly IRobotModelService _robotModelService = robotModelService;
|
|
private readonly IVehicleTypeService _vehicleTypeService = vehicleTypeService;
|
|
private readonly IMapQueryService _mapQueryService = mapQueryService;
|
|
private readonly ILayoutService _layoutService = layoutService;
|
|
private readonly Logger<RobotModelMapService> _logger = logger;
|
|
|
|
public async Task<List<Node>> GetFilteredNodesAsync(Guid robotModelId)
|
|
{
|
|
// Get RobotModel
|
|
var robotModel = await _robotModelService.GetByIdAsync(robotModelId)
|
|
?? throw new KeyNotFoundException($"RobotModel with ID {robotModelId} not found.");
|
|
|
|
// Validate VehicleTypeId is set
|
|
if (!robotModel.VehicleTypeId.HasValue)
|
|
{
|
|
throw new InvalidOperationException($"RobotModel '{robotModel.ModelName}' does not have VehicleTypeId set.");
|
|
}
|
|
|
|
var vehicleTypeId = robotModel.VehicleTypeId.Value;
|
|
|
|
// Validate VehicleType exists
|
|
var vehicleType = await _vehicleTypeService.GetByIdAsync(vehicleTypeId)
|
|
?? throw new KeyNotFoundException($"VehicleType with ID {vehicleTypeId} not found.");
|
|
|
|
if (!vehicleType.IsActive)
|
|
{
|
|
_logger.Warning($"VehicleType '{vehicleType.VehicleTypeName}' is not active.");
|
|
}
|
|
|
|
// Get filtered nodes: nodes that have NodeVehicleProperties for this VehicleType
|
|
var filteredNodes = await _mapQueryService.GetNodesByVehicleTypeAsync(vehicleTypeId);
|
|
|
|
_logger.Info($"Found {filteredNodes.Count} filtered nodes for RobotModel '{robotModel.ModelName}' (VehicleType: '{vehicleType.VehicleTypeName}')");
|
|
|
|
return filteredNodes;
|
|
}
|
|
|
|
public async Task<List<Edge>> GetFilteredEdgesAsync(Guid robotModelId)
|
|
{
|
|
// Get RobotModel
|
|
var robotModel = await _robotModelService.GetByIdAsync(robotModelId)
|
|
?? throw new KeyNotFoundException($"RobotModel with ID {robotModelId} not found.");
|
|
|
|
// Validate VehicleTypeId is set
|
|
if (!robotModel.VehicleTypeId.HasValue)
|
|
{
|
|
throw new InvalidOperationException($"RobotModel '{robotModel.ModelName}' does not have VehicleTypeId set.");
|
|
}
|
|
|
|
var vehicleTypeId = robotModel.VehicleTypeId.Value;
|
|
|
|
// Validate VehicleType exists
|
|
var vehicleType = await _vehicleTypeService.GetByIdAsync(vehicleTypeId)
|
|
?? throw new KeyNotFoundException($"VehicleType with ID {vehicleTypeId} not found.");
|
|
|
|
// Get filtered edges: edges that have EdgeVehicleProperties for this VehicleType
|
|
var filteredEdges = await _mapQueryService.GetEdgesByVehicleTypeAsync(vehicleTypeId);
|
|
|
|
_logger.Info($"Found {filteredEdges.Count} filtered edges for RobotModel '{robotModel.ModelName}' (VehicleType: '{vehicleType.VehicleTypeName}')");
|
|
|
|
return filteredEdges;
|
|
}
|
|
|
|
public async Task<List<Node>> GetFilteredNodesByLevelAsync(Guid robotModelId, Guid levelId)
|
|
{
|
|
// Get RobotModel
|
|
var robotModel = await _robotModelService.GetByIdAsync(robotModelId)
|
|
?? throw new KeyNotFoundException($"RobotModel with ID {robotModelId} not found.");
|
|
|
|
// Validate VehicleTypeId is set
|
|
if (!robotModel.VehicleTypeId.HasValue)
|
|
{
|
|
throw new InvalidOperationException($"RobotModel '{robotModel.ModelName}' does not have VehicleTypeId set.");
|
|
}
|
|
|
|
var vehicleTypeId = robotModel.VehicleTypeId.Value;
|
|
|
|
// Validate VehicleType exists
|
|
var vehicleType = await _vehicleTypeService.GetByIdAsync(vehicleTypeId)
|
|
?? throw new KeyNotFoundException($"VehicleType with ID {vehicleTypeId} not found.");
|
|
|
|
if (!vehicleType.IsActive)
|
|
{
|
|
_logger.Warning($"VehicleType '{vehicleType.VehicleTypeName}' is not active.");
|
|
}
|
|
|
|
// Get filtered nodes: nodes that have NodeVehicleProperties for this VehicleType and belong to the specified level
|
|
var filteredNodes = await _mapQueryService.GetNodesByVehicleTypeAndLevelAsync(vehicleTypeId, levelId);
|
|
|
|
_logger.Info($"Found {filteredNodes.Count} filtered nodes for RobotModel '{robotModel.ModelName}' (VehicleType: '{vehicleType.VehicleTypeName}') in LevelId={levelId}");
|
|
|
|
return filteredNodes;
|
|
}
|
|
|
|
public async Task<List<Edge>> GetFilteredEdgesByLevelAsync(Guid robotModelId, Guid levelId)
|
|
{
|
|
// Get RobotModel
|
|
var robotModel = await _robotModelService.GetByIdAsync(robotModelId)
|
|
?? throw new KeyNotFoundException($"RobotModel with ID {robotModelId} not found.");
|
|
|
|
// Validate VehicleTypeId is set
|
|
if (!robotModel.VehicleTypeId.HasValue)
|
|
{
|
|
throw new InvalidOperationException($"RobotModel '{robotModel.ModelName}' does not have VehicleTypeId set.");
|
|
}
|
|
|
|
var vehicleTypeId = robotModel.VehicleTypeId.Value;
|
|
|
|
// Validate VehicleType exists
|
|
var vehicleType = await _vehicleTypeService.GetByIdAsync(vehicleTypeId)
|
|
?? throw new KeyNotFoundException($"VehicleType with ID {vehicleTypeId} not found.");
|
|
|
|
// Get filtered edges: edges that have EdgeVehicleProperties for this VehicleType and belong to the specified level
|
|
var filteredEdges = await _mapQueryService.GetEdgesByVehicleTypeAndLevelAsync(vehicleTypeId, levelId);
|
|
|
|
_logger.Info($"Found {filteredEdges.Count} filtered edges for RobotModel '{robotModel.ModelName}' (VehicleType: '{vehicleType.VehicleTypeName}') in LevelId={levelId}");
|
|
|
|
return filteredEdges;
|
|
}
|
|
|
|
public async Task<RobotModelMapDataDto> GetValidatedMapDataAsync(Guid robotModelId)
|
|
{
|
|
// Get RobotModel
|
|
var robotModel = await _robotModelService.GetByIdAsync(robotModelId)
|
|
?? throw new KeyNotFoundException($"RobotModel with ID {robotModelId} not found.");
|
|
|
|
// Validate VehicleTypeId is set
|
|
if (!robotModel.VehicleTypeId.HasValue)
|
|
{
|
|
throw new InvalidOperationException($"RobotModel '{robotModel.ModelName}' does not have VehicleTypeId set.");
|
|
}
|
|
|
|
var vehicleTypeId = robotModel.VehicleTypeId.Value;
|
|
|
|
// Get VehicleType
|
|
var vehicleType = await _vehicleTypeService.GetByIdAsync(vehicleTypeId)
|
|
?? throw new KeyNotFoundException($"VehicleType with ID {vehicleTypeId} not found.");
|
|
|
|
// Get filtered nodes and edges (from all levels that match VehicleType)
|
|
// Note: If RobotModel needs to filter by specific LevelId, MapId should be added to RobotModel
|
|
var filteredNodes = await GetFilteredNodesAsync(robotModelId);
|
|
var filteredEdges = await GetFilteredEdgesAsync(robotModelId);
|
|
|
|
// Get total counts across all levels (for reference)
|
|
// In the future, if RobotModel has MapId (LevelId), we can filter by specific level
|
|
int totalNodesInLevel = await _mapQueryService.GetTotalNodesCountAsync();
|
|
int totalEdgesInLevel = await _mapQueryService.GetTotalEdgesCountAsync();
|
|
string? levelName = null;
|
|
Guid? levelId = null;
|
|
|
|
// If we have filtered nodes, get the level from first node (for display)
|
|
if (filteredNodes.Count > 0)
|
|
{
|
|
var firstNode = filteredNodes.First();
|
|
var level = await _layoutService.GetLevelAsync(firstNode.LevelId);
|
|
levelName = level?.LayoutLevelId;
|
|
levelId = level?.Id;
|
|
}
|
|
|
|
// Validate: Remove nodes without edges, edges without both nodes
|
|
var nodeIds = new HashSet<Guid>(filteredNodes.Select(n => n.Id));
|
|
var validNodeIds = new HashSet<Guid>();
|
|
var validEdges = new List<Edge>();
|
|
|
|
// First pass: Find edges that have both start and end nodes in filtered nodes
|
|
foreach (var edge in filteredEdges)
|
|
{
|
|
if (nodeIds.Contains(edge.StartNodeId) && nodeIds.Contains(edge.EndNodeId))
|
|
{
|
|
validEdges.Add(edge);
|
|
validNodeIds.Add(edge.StartNodeId);
|
|
validNodeIds.Add(edge.EndNodeId);
|
|
}
|
|
}
|
|
|
|
// Second pass: Keep only nodes that are connected by valid edges
|
|
var validNodes = filteredNodes.Where(n => validNodeIds.Contains(n.Id)).ToList();
|
|
|
|
// Count removed items
|
|
int nodesRemoved = filteredNodes.Count - validNodes.Count;
|
|
int edgesRemoved = filteredEdges.Count - validEdges.Count;
|
|
|
|
// Build validation result
|
|
var validationResult = new MapValidationResultDto
|
|
{
|
|
IsValid = nodesRemoved == 0 && edgesRemoved == 0,
|
|
NodesRemoved = nodesRemoved,
|
|
EdgesRemoved = edgesRemoved
|
|
};
|
|
|
|
// Add errors for removed nodes
|
|
foreach (var node in filteredNodes.Where(n => !validNodeIds.Contains(n.Id)))
|
|
{
|
|
validationResult.Errors.Add(new ValidationError
|
|
{
|
|
Code = "NODE_NO_EDGES",
|
|
Message = $"Node '{node.NodeId}' has no connected edges in the filtered map",
|
|
EntityId = node.Id.ToString(),
|
|
EntityType = "Node"
|
|
});
|
|
}
|
|
|
|
// Add errors for removed edges
|
|
foreach (var edge in filteredEdges.Where(e => !validEdges.Contains(e)))
|
|
{
|
|
var missingStart = !nodeIds.Contains(edge.StartNodeId);
|
|
var missingEnd = !nodeIds.Contains(edge.EndNodeId);
|
|
|
|
if (missingStart && missingEnd)
|
|
{
|
|
validationResult.Errors.Add(new ValidationError
|
|
{
|
|
Code = "EDGE_MISSING_BOTH_NODES",
|
|
Message = $"Edge '{edge.EdgeId}' is missing both start and end nodes",
|
|
EntityId = edge.Id.ToString(),
|
|
EntityType = "Edge"
|
|
});
|
|
}
|
|
else if (missingStart)
|
|
{
|
|
validationResult.Errors.Add(new ValidationError
|
|
{
|
|
Code = "EDGE_MISSING_START_NODE",
|
|
Message = $"Edge '{edge.EdgeId}' is missing start node",
|
|
EntityId = edge.Id.ToString(),
|
|
EntityType = "Edge"
|
|
});
|
|
}
|
|
else if (missingEnd)
|
|
{
|
|
validationResult.Errors.Add(new ValidationError
|
|
{
|
|
Code = "EDGE_MISSING_END_NODE",
|
|
Message = $"Edge '{edge.EdgeId}' is missing end node",
|
|
EntityId = edge.Id.ToString(),
|
|
EntityType = "Edge"
|
|
});
|
|
}
|
|
}
|
|
|
|
// Convert to DTOs
|
|
var nodeDtos = validNodes.Select(n => new NodeDto
|
|
{
|
|
Id = n.Id,
|
|
LevelId = n.LevelId,
|
|
NodeId = n.NodeId,
|
|
NodeName = n.NodeName,
|
|
NodeDescription = n.NodeDescription,
|
|
MapId = n.MapId,
|
|
X = n.X,
|
|
Y = n.Y,
|
|
VehicleProperties = [.. n.VehicleProperties
|
|
.Where(vp => vp.VehicleTypeId == vehicleTypeId)
|
|
.Select(vp => new NodeVehiclePropertyDto
|
|
{
|
|
Id = vp.Id,
|
|
NodeId = vp.NodeId,
|
|
VehicleTypeId = vp.VehicleTypeId,
|
|
Theta = vp.Theta,
|
|
Actions = vp.Actions
|
|
})]
|
|
}).ToList();
|
|
|
|
var edgeDtos = validEdges.Select(e => new EdgeDto
|
|
{
|
|
Id = e.Id,
|
|
LevelId = e.LevelId,
|
|
EdgeId = e.EdgeId,
|
|
EdgeName = e.EdgeName,
|
|
EdgeDescription = e.EdgeDescription,
|
|
StartNodeId = e.StartNodeId,
|
|
EndNodeId = e.EndNodeId,
|
|
StartNode = nodeDtos.FirstOrDefault(n => n.Id == e.StartNodeId),
|
|
EndNode = nodeDtos.FirstOrDefault(n => n.Id == e.EndNodeId),
|
|
VehicleProperties = [.. e.VehicleProperties
|
|
.Where(vp => vp.VehicleTypeId == vehicleTypeId)
|
|
.Select(vp => new EdgeVehiclePropertyDto
|
|
{
|
|
Id = vp.Id,
|
|
EdgeId = vp.EdgeId,
|
|
VehicleTypeId = vp.VehicleTypeId,
|
|
VehicleOrientation = vp.VehicleOrientation,
|
|
OrientationType = vp.OrientationType,
|
|
RotationAllowed = vp.RotationAllowed,
|
|
RotationAtStartNodeAllowed = vp.RotationAtStartNodeAllowed,
|
|
RotationAtEndNodeAllowed = vp.RotationAtEndNodeAllowed,
|
|
MaxSpeed = vp.MaxSpeed,
|
|
MaxRotationSpeed = vp.MaxRotationSpeed,
|
|
MinHeight = vp.MinHeight,
|
|
MaxHeight = vp.MaxHeight,
|
|
LoadRestriction = null,
|
|
Actions = vp.Actions
|
|
})]
|
|
}).ToList();
|
|
|
|
var result = new RobotModelMapDataDto
|
|
{
|
|
RobotModelId = robotModel.Id,
|
|
RobotModelName = robotModel.ModelName,
|
|
VehicleTypeId = vehicleTypeId,
|
|
VehicleTypeName = vehicleType.VehicleTypeName,
|
|
LevelId = levelId,
|
|
LevelName = levelName,
|
|
ValidNodes = nodeDtos,
|
|
ValidEdges = edgeDtos,
|
|
TotalNodesInLevel = totalNodesInLevel,
|
|
TotalEdgesInLevel = totalEdgesInLevel,
|
|
FilteredNodesCount = filteredNodes.Count,
|
|
FilteredEdgesCount = filteredEdges.Count,
|
|
ValidationResult = validationResult
|
|
};
|
|
|
|
_logger.Info($"Validated map data for RobotModel '{robotModel.ModelName}': {validNodes.Count} valid nodes, {validEdges.Count} valid edges (removed {nodesRemoved} nodes, {edgesRemoved} edges)");
|
|
|
|
return result;
|
|
}
|
|
|
|
public async Task<MapValidationResultDto> ValidateMapForRobotModelAsync(Guid robotModelId)
|
|
{
|
|
var mapData = await GetValidatedMapDataAsync(robotModelId);
|
|
return mapData.ValidationResult;
|
|
}
|
|
|
|
public async Task<bool> HasValidMapConfigurationAsync(Guid robotModelId)
|
|
{
|
|
try
|
|
{
|
|
var robotModel = await _robotModelService.GetByIdAsync(robotModelId);
|
|
if (robotModel == null)
|
|
return false;
|
|
|
|
if (!robotModel.VehicleTypeId.HasValue)
|
|
return false;
|
|
|
|
// Check if VehicleType exists and is active
|
|
var vehicleType = await _vehicleTypeService.GetByIdAsync(robotModel.VehicleTypeId.Value);
|
|
|
|
if (vehicleType == null || !vehicleType.IsActive)
|
|
return false;
|
|
|
|
// Validate map data
|
|
var validationResult = await ValidateMapForRobotModelAsync(robotModelId);
|
|
return validationResult.IsValid;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.Error($"Error checking valid map configuration for RobotModel {robotModelId}: {ex.Message}");
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|