using Microsoft.Extensions.Logging; using SatelliteProvider.Common.DTO; using SatelliteProvider.Common.Interfaces; using SatelliteProvider.Common.Utils; using SatelliteProvider.DataAccess.Models; using SatelliteProvider.DataAccess.Repositories; namespace SatelliteProvider.Services; public class RouteService : IRouteService { private readonly IRouteRepository _routeRepository; private readonly ILogger _logger; private const double MAX_POINT_SPACING_METERS = 200.0; public RouteService( IRouteRepository routeRepository, ILogger logger) { _routeRepository = routeRepository; _logger = logger; } public async Task CreateRouteAsync(CreateRouteRequest request) { if (request.Points.Count < 2) { throw new ArgumentException("Route must have at least 2 points"); } if (request.RegionSizeMeters < 100 || request.RegionSizeMeters > 10000) { throw new ArgumentException("Region size must be between 100 and 10000 meters"); } if (string.IsNullOrWhiteSpace(request.Name)) { throw new ArgumentException("Route name is required"); } _logger.LogInformation("Creating route {RouteId} with {PointCount} original points", request.Id, request.Points.Count); var allPoints = new List(); var totalDistance = 0.0; var sequenceNumber = 0; for (int segmentIndex = 0; segmentIndex < request.Points.Count; segmentIndex++) { var currentPoint = request.Points[segmentIndex]; var isStart = segmentIndex == 0; var isEnd = segmentIndex == request.Points.Count - 1; var geoPoint = new GeoPoint(currentPoint.Latitude, currentPoint.Longitude); double? distanceFromPrevious = null; if (segmentIndex > 0) { var prevPoint = request.Points[segmentIndex - 1]; var prevGeoPoint = new GeoPoint(prevPoint.Latitude, prevPoint.Longitude); distanceFromPrevious = GeoUtils.CalculateDistance(prevGeoPoint, geoPoint); totalDistance += distanceFromPrevious.Value; } var pointType = isStart ? "start" : (isEnd ? "end" : "waypoint"); allPoints.Add(new RoutePointDto { Latitude = currentPoint.Latitude, Longitude = currentPoint.Longitude, PointType = pointType, SequenceNumber = sequenceNumber++, SegmentIndex = segmentIndex, DistanceFromPrevious = distanceFromPrevious }); if (!isEnd) { var nextPoint = request.Points[segmentIndex + 1]; var startGeo = new GeoPoint(currentPoint.Latitude, currentPoint.Longitude); var endGeo = new GeoPoint(nextPoint.Latitude, nextPoint.Longitude); var intermediatePoints = GeoUtils.CalculateIntermediatePoints(startGeo, endGeo, MAX_POINT_SPACING_METERS); _logger.LogInformation("Segment {SegmentIndex}: Adding {Count} intermediate points", segmentIndex, intermediatePoints.Count); foreach (var intermediateGeo in intermediatePoints) { var prevGeo = sequenceNumber == 1 ? startGeo : new GeoPoint( allPoints[sequenceNumber - 1].Latitude, allPoints[sequenceNumber - 1].Longitude); var distFromPrev = GeoUtils.CalculateDistance(prevGeo, intermediateGeo); totalDistance += distFromPrev; allPoints.Add(new RoutePointDto { Latitude = intermediateGeo.Lat, Longitude = intermediateGeo.Lon, PointType = "intermediate", SequenceNumber = sequenceNumber++, SegmentIndex = segmentIndex, DistanceFromPrevious = distFromPrev }); } } } _logger.LogInformation("Route {RouteId}: Total {TotalPoints} points (original + intermediate), distance {Distance:F2}m", request.Id, allPoints.Count, totalDistance); var now = DateTime.UtcNow; var routeEntity = new RouteEntity { Id = request.Id, Name = request.Name, Description = request.Description, RegionSizeMeters = request.RegionSizeMeters, ZoomLevel = request.ZoomLevel, TotalDistanceMeters = totalDistance, TotalPoints = allPoints.Count, CreatedAt = now, UpdatedAt = now }; await _routeRepository.InsertRouteAsync(routeEntity); var pointEntities = allPoints.Select(p => new RoutePointEntity { Id = Guid.NewGuid(), RouteId = request.Id, SequenceNumber = p.SequenceNumber, Latitude = p.Latitude, Longitude = p.Longitude, PointType = p.PointType, SegmentIndex = p.SegmentIndex, DistanceFromPrevious = p.DistanceFromPrevious, CreatedAt = now }).ToList(); await _routeRepository.InsertRoutePointsAsync(pointEntities); _logger.LogInformation("Route {RouteId} created successfully", request.Id); return new RouteResponse { Id = routeEntity.Id, Name = routeEntity.Name, Description = routeEntity.Description, RegionSizeMeters = routeEntity.RegionSizeMeters, ZoomLevel = routeEntity.ZoomLevel, TotalDistanceMeters = routeEntity.TotalDistanceMeters, TotalPoints = routeEntity.TotalPoints, Points = allPoints, CreatedAt = routeEntity.CreatedAt, UpdatedAt = routeEntity.UpdatedAt }; } public async Task GetRouteAsync(Guid id) { var route = await _routeRepository.GetByIdAsync(id); if (route == null) { return null; } var points = await _routeRepository.GetRoutePointsAsync(id); return new RouteResponse { Id = route.Id, Name = route.Name, Description = route.Description, RegionSizeMeters = route.RegionSizeMeters, ZoomLevel = route.ZoomLevel, TotalDistanceMeters = route.TotalDistanceMeters, TotalPoints = route.TotalPoints, Points = points.Select(p => new RoutePointDto { Latitude = p.Latitude, Longitude = p.Longitude, PointType = p.PointType, SequenceNumber = p.SequenceNumber, SegmentIndex = p.SegmentIndex, DistanceFromPrevious = p.DistanceFromPrevious }).ToList(), CreatedAt = route.CreatedAt, UpdatedAt = route.UpdatedAt }; } }