route in progress, region stitching is disabled by default

This commit is contained in:
Anton Martynenko
2025-11-01 15:55:41 +01:00
parent b532f1335e
commit 8714a4817d
23 changed files with 743 additions and 18 deletions
+15 -5
View File
@@ -34,7 +34,7 @@ public class RegionService : IRegionService
_logger = logger;
}
public async Task<RegionStatus> RequestRegionAsync(Guid id, double latitude, double longitude, double sizeMeters, int zoomLevel)
public async Task<RegionStatus> RequestRegionAsync(Guid id, double latitude, double longitude, double sizeMeters, int zoomLevel, bool stitchTiles = false)
{
var now = DateTime.UtcNow;
var region = new RegionEntity
@@ -44,6 +44,7 @@ public class RegionService : IRegionService
Longitude = longitude,
SizeMeters = sizeMeters,
ZoomLevel = zoomLevel,
StitchTiles = stitchTiles,
Status = "queued",
TilesDownloaded = 0,
TilesReused = 0,
@@ -59,7 +60,8 @@ public class RegionService : IRegionService
Latitude = latitude,
Longitude = longitude,
SizeMeters = sizeMeters,
ZoomLevel = zoomLevel
ZoomLevel = zoomLevel,
StitchTiles = stitchTiles
};
await _queue.EnqueueAsync(request);
@@ -134,12 +136,20 @@ public class RegionService : IRegionService
var csvPath = Path.Combine(readyDir, $"region_{id}_ready.csv");
var summaryPath = Path.Combine(readyDir, $"region_{id}_summary.txt");
var stitchedImagePath = Path.Combine(readyDir, $"region_{id}_stitched.jpg");
string? stitchedImagePath = null;
await GenerateCsvFileAsync(csvPath, tiles, linkedCts.Token);
_logger.LogInformation("Stitching tiles for region {RegionId}", id);
await StitchTilesAsync(tiles, region.Latitude, region.Longitude, region.ZoomLevel, stitchedImagePath, linkedCts.Token);
if (region.StitchTiles)
{
stitchedImagePath = Path.Combine(readyDir, $"region_{id}_stitched.jpg");
_logger.LogInformation("Stitching tiles for region {RegionId}", id);
await StitchTilesAsync(tiles, region.Latitude, region.Longitude, region.ZoomLevel, stitchedImagePath, linkedCts.Token);
}
else
{
_logger.LogInformation("Skipping tile stitching for region {RegionId}", id);
}
await GenerateSummaryFileAsync(summaryPath, id, region, tiles, tilesDownloaded, tilesReused, stitchedImagePath, processingStartTime, linkedCts.Token, errorMessage);
+194
View File
@@ -0,0 +1,194 @@
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<RouteService> _logger;
private const double MAX_POINT_SPACING_METERS = 200.0;
public RouteService(
IRouteRepository routeRepository,
ILogger<RouteService> logger)
{
_routeRepository = routeRepository;
_logger = logger;
}
public async Task<RouteResponse> 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<RoutePointDto>();
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<RouteResponse?> 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
};
}
}