Initial commit
This commit is contained in:
@@ -0,0 +1,370 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user