Files
I150/srcs/RobotNet10/Commons/RobotNet10.MapManager/Services/NodeService.cs
2026-07-03 16:37:12 +07:00

151 lines
5.2 KiB
C#

using Microsoft.EntityFrameworkCore;
using RobotNet10.MapEditor.Shared.DTOs.Requests;
using RobotNet10.MapManager.Data;
namespace RobotNet10.MapManager.Services;
/// <summary>
/// Service implementation for managing nodes
/// </summary>
public class NodeService(MapDbContext context) : INodeService
{
private readonly MapDbContext _context = context;
public async Task<List<Node>> GetNodesByLevelAsync(Guid layoutLevelId, bool includeVehicleProperties = true)
{
var query = _context.Nodes.Where(n => n.LevelId == layoutLevelId);
if (includeVehicleProperties)
{
query = query.Include(n => n.VehicleProperties)
.ThenInclude(vp => vp.VehicleType);
}
return await query.OrderBy(n => n.NodeId).ToListAsync();
}
public async Task<Node?> GetByIdAsync(Guid nodeId, bool includeVehicleProperties = true)
{
var query = _context.Nodes.Where(n => n.Id == nodeId);
if (includeVehicleProperties)
{
query = query.Include(n => n.VehicleProperties)
.ThenInclude(vp => vp.VehicleType);
}
return await query.FirstOrDefaultAsync();
}
public async Task<Node> UpdateAsync(Guid nodeId, UpdateNodeRequest request)
{
var node = await GetByIdAsync(nodeId, includeVehicleProperties: true) ??
throw new InvalidOperationException($"Node with ID '{nodeId}' not found");
// Update coordinates if provided
if (request.X.HasValue)
{
// Validate bounds
if (!await ValidateCoordinatesAsync(node.LevelId, request.X.Value, node.Y))
{
throw new InvalidOperationException($"Coordinates ({request.X.Value}, {node.Y}) are outside valid bounds");
}
node.X = request.X.Value;
}
if (request.Y.HasValue)
{
// Validate bounds
if (!await ValidateCoordinatesAsync(node.LevelId, node.X, request.Y.Value))
{
throw new InvalidOperationException($"Coordinates ({node.X}, {request.Y.Value}) are outside valid bounds");
}
node.Y = request.Y.Value;
}
// Update other properties
if (request.NodeName != null)
node.NodeName = request.NodeName;
if (request.NodeDescription != null)
node.NodeDescription = request.NodeDescription;
if (request.MapId != null)
node.MapId = request.MapId;
// Update vehicle properties if provided
if (request.VehicleProperties != null)
{
// Remove existing properties
var existingProps = await _context.NodeVehicleProperties
.Where(nvp => nvp.NodeId == nodeId)
.ToListAsync();
_context.NodeVehicleProperties.RemoveRange(existingProps);
// Add new properties
foreach (var propDto in request.VehicleProperties)
{
var prop = new NodeVehicleProperty
{
NodeId = nodeId,
VehicleTypeId = propDto.VehicleTypeId,
Theta = propDto.Theta,
Actions = propDto.Actions,
AllowedDeviationXY = propDto.AllowedDeviationXY,
AllowedDeviationTheta = propDto.AllowedDeviationTheta
};
_context.NodeVehicleProperties.Add(prop);
}
}
await _context.SaveChangesAsync();
// Reload with vehicle properties
return (await GetByIdAsync(nodeId, includeVehicleProperties: true))!;
}
public async Task<bool> ValidateCoordinatesAsync(Guid layoutLevelId, double x, double y)
{
var settings = await _context.LayoutLevelEditorSettings
.FirstOrDefaultAsync(s => s.LevelId == layoutLevelId);
if (settings == null)
return true; // No bounds set, allow any coordinates
// Check bounds
if (settings.BoundsMinX.HasValue && x < settings.BoundsMinX.Value)
return false;
if (settings.BoundsMaxX.HasValue && x > settings.BoundsMaxX.Value)
return false;
if (settings.BoundsMinY.HasValue && y < settings.BoundsMinY.Value)
return false;
if (settings.BoundsMaxY.HasValue && y > settings.BoundsMaxY.Value)
return false;
return true;
}
public async Task<List<Node>> FindNodesNearCoordinatesAsync(Guid layoutLevelId, double x, double y, double radius)
{
// Pre-filter with bounding box at database level, then refine with Euclidean distance in-memory
var nodes = await _context.Nodes
.Where(n => n.LevelId == layoutLevelId
&& n.X >= x - radius && n.X <= x + radius
&& n.Y >= y - radius && n.Y <= y + radius)
.ToListAsync();
return [.. nodes
.Where(n =>
{
var dx = n.X - x;
var dy = n.Y - y;
return dx * dx + dy * dy <= radius * radius;
})
.OrderBy(n => (n.X - x) * (n.X - x) + (n.Y - y) * (n.Y - y))];
}
}