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