mirror of
https://github.com/azaion/satellite-provider.git
synced 2026-04-22 07:06:39 +00:00
214 lines
8.1 KiB
C#
214 lines
8.1 KiB
C#
using Microsoft.Extensions.Logging;
|
|
using Microsoft.Extensions.Options;
|
|
using SatelliteProvider.Common.Configs;
|
|
using SatelliteProvider.Common.DTO;
|
|
using SatelliteProvider.Common.Interfaces;
|
|
using SatelliteProvider.DataAccess.Models;
|
|
using SatelliteProvider.DataAccess.Repositories;
|
|
|
|
namespace SatelliteProvider.Services;
|
|
|
|
public class RegionService : IRegionService
|
|
{
|
|
private readonly IRegionRepository _regionRepository;
|
|
private readonly IRegionRequestQueue _queue;
|
|
private readonly ITileService _tileService;
|
|
private readonly StorageConfig _storageConfig;
|
|
private readonly ILogger<RegionService> _logger;
|
|
|
|
public RegionService(
|
|
IRegionRepository regionRepository,
|
|
IRegionRequestQueue queue,
|
|
ITileService tileService,
|
|
IOptions<StorageConfig> storageConfig,
|
|
ILogger<RegionService> logger)
|
|
{
|
|
_regionRepository = regionRepository;
|
|
_queue = queue;
|
|
_tileService = tileService;
|
|
_storageConfig = storageConfig.Value;
|
|
_logger = logger;
|
|
}
|
|
|
|
public async Task<RegionStatus> RequestRegionAsync(Guid id, double latitude, double longitude, double sizeMeters, int zoomLevel)
|
|
{
|
|
var now = DateTime.UtcNow;
|
|
var region = new RegionEntity
|
|
{
|
|
Id = id,
|
|
Latitude = latitude,
|
|
Longitude = longitude,
|
|
SizeMeters = sizeMeters,
|
|
ZoomLevel = zoomLevel,
|
|
Status = "queued",
|
|
TilesDownloaded = 0,
|
|
TilesReused = 0,
|
|
CreatedAt = now,
|
|
UpdatedAt = now
|
|
};
|
|
|
|
await _regionRepository.InsertAsync(region);
|
|
|
|
var request = new RegionRequest
|
|
{
|
|
Id = id,
|
|
Latitude = latitude,
|
|
Longitude = longitude,
|
|
SizeMeters = sizeMeters,
|
|
ZoomLevel = zoomLevel
|
|
};
|
|
|
|
await _queue.EnqueueAsync(request);
|
|
|
|
_logger.LogInformation("Region {RegionId} queued for processing", id);
|
|
|
|
return MapToStatus(region);
|
|
}
|
|
|
|
public async Task<RegionStatus?> GetRegionStatusAsync(Guid id)
|
|
{
|
|
var region = await _regionRepository.GetByIdAsync(id);
|
|
return region != null ? MapToStatus(region) : null;
|
|
}
|
|
|
|
public async Task ProcessRegionAsync(Guid id, CancellationToken cancellationToken = default)
|
|
{
|
|
_logger.LogInformation("Processing region {RegionId}", id);
|
|
var startTime = DateTime.UtcNow;
|
|
|
|
var region = await _regionRepository.GetByIdAsync(id);
|
|
if (region == null)
|
|
{
|
|
_logger.LogWarning("Region {RegionId} not found", id);
|
|
return;
|
|
}
|
|
|
|
region.Status = "processing";
|
|
region.UpdatedAt = DateTime.UtcNow;
|
|
await _regionRepository.UpdateAsync(region);
|
|
|
|
try
|
|
{
|
|
_logger.LogInformation("Downloading tiles for region {RegionId} at ({Lat}, {Lon}) size {Size}m zoom {Zoom}",
|
|
id, region.Latitude, region.Longitude, region.SizeMeters, region.ZoomLevel);
|
|
|
|
var processingStartTime = DateTime.UtcNow;
|
|
|
|
_logger.LogInformation("Checking for existing tiles in region {RegionId}", id);
|
|
var tilesBeforeDownload = await _tileService.GetTilesByRegionAsync(
|
|
region.Latitude,
|
|
region.Longitude,
|
|
region.SizeMeters,
|
|
region.ZoomLevel);
|
|
var existingTileIds = new HashSet<Guid>(tilesBeforeDownload.Select(t => t.Id));
|
|
_logger.LogInformation("Found {Count} existing tiles for region {RegionId}", existingTileIds.Count, id);
|
|
|
|
_logger.LogInformation("Starting tile download for region {RegionId}", id);
|
|
var tiles = await _tileService.DownloadAndStoreTilesAsync(
|
|
region.Latitude,
|
|
region.Longitude,
|
|
region.SizeMeters,
|
|
region.ZoomLevel,
|
|
cancellationToken);
|
|
|
|
var tilesDownloaded = tiles.Count(t => !existingTileIds.Contains(t.Id));
|
|
var tilesReused = tiles.Count(t => existingTileIds.Contains(t.Id));
|
|
|
|
_logger.LogInformation("Region {RegionId}: Downloaded {Downloaded} tiles, Reused {Reused} tiles",
|
|
id, tilesDownloaded, tilesReused);
|
|
|
|
var readyDir = _storageConfig.ReadyDirectory;
|
|
Directory.CreateDirectory(readyDir);
|
|
|
|
var csvPath = Path.Combine(readyDir, $"region_{id}_ready.csv");
|
|
var summaryPath = Path.Combine(readyDir, $"region_{id}_summary.txt");
|
|
|
|
await GenerateCsvFileAsync(csvPath, tiles, cancellationToken);
|
|
await GenerateSummaryFileAsync(summaryPath, id, region, tiles, tilesDownloaded, tilesReused, processingStartTime, cancellationToken);
|
|
|
|
region.Status = "completed";
|
|
region.CsvFilePath = csvPath;
|
|
region.SummaryFilePath = summaryPath;
|
|
region.TilesDownloaded = tilesDownloaded;
|
|
region.TilesReused = tilesReused;
|
|
region.UpdatedAt = DateTime.UtcNow;
|
|
await _regionRepository.UpdateAsync(region);
|
|
|
|
var duration = (DateTime.UtcNow - startTime).TotalSeconds;
|
|
_logger.LogInformation("Region {RegionId} processing completed in {Duration:F2}s", id, duration);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Failed to process region {RegionId}: {Message}", id, ex.Message);
|
|
region.Status = "failed";
|
|
region.UpdatedAt = DateTime.UtcNow;
|
|
await _regionRepository.UpdateAsync(region);
|
|
}
|
|
}
|
|
|
|
private async Task GenerateCsvFileAsync(string filePath, List<TileMetadata> tiles, CancellationToken cancellationToken)
|
|
{
|
|
var orderedTiles = tiles.OrderByDescending(t => t.Latitude).ThenBy(t => t.Longitude).ToList();
|
|
|
|
using var writer = new StreamWriter(filePath);
|
|
await writer.WriteLineAsync("latitude,longitude,file_path");
|
|
|
|
foreach (var tile in orderedTiles)
|
|
{
|
|
await writer.WriteLineAsync($"{tile.Latitude:F6},{tile.Longitude:F6},{tile.FilePath}");
|
|
}
|
|
}
|
|
|
|
private async Task GenerateSummaryFileAsync(
|
|
string filePath,
|
|
Guid regionId,
|
|
RegionEntity region,
|
|
List<TileMetadata> tiles,
|
|
int tilesDownloaded,
|
|
int tilesReused,
|
|
DateTime startTime,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
var endTime = DateTime.UtcNow;
|
|
var processingTime = (endTime - startTime).TotalSeconds;
|
|
|
|
var summary = new System.Text.StringBuilder();
|
|
summary.AppendLine("Region Processing Summary");
|
|
summary.AppendLine("========================");
|
|
summary.AppendLine($"Region ID: {regionId}");
|
|
summary.AppendLine($"Center: {region.Latitude:F6}, {region.Longitude:F6}");
|
|
summary.AppendLine($"Size: {region.SizeMeters:F0} meters");
|
|
summary.AppendLine($"Zoom Level: {region.ZoomLevel}");
|
|
summary.AppendLine();
|
|
summary.AppendLine("Processing Statistics:");
|
|
summary.AppendLine($"- Tiles Downloaded: {tilesDownloaded}");
|
|
summary.AppendLine($"- Tiles Reused from Cache: {tilesReused}");
|
|
summary.AppendLine($"- Total Tiles: {tiles.Count}");
|
|
summary.AppendLine($"- Processing Time: {processingTime:F2} seconds");
|
|
summary.AppendLine($"- Started: {startTime:yyyy-MM-dd HH:mm:ss} UTC");
|
|
summary.AppendLine($"- Completed: {endTime:yyyy-MM-dd HH:mm:ss} UTC");
|
|
summary.AppendLine();
|
|
summary.AppendLine("Files Created:");
|
|
summary.AppendLine($"- CSV: {Path.GetFileName(filePath).Replace("_summary.txt", "_ready.csv")}");
|
|
summary.AppendLine($"- Summary: {Path.GetFileName(filePath)}");
|
|
|
|
await File.WriteAllTextAsync(filePath, summary.ToString(), cancellationToken);
|
|
}
|
|
|
|
private static RegionStatus MapToStatus(RegionEntity region)
|
|
{
|
|
return new RegionStatus
|
|
{
|
|
Id = region.Id,
|
|
Status = region.Status,
|
|
CsvFilePath = region.CsvFilePath,
|
|
SummaryFilePath = region.SummaryFilePath,
|
|
TilesDownloaded = region.TilesDownloaded,
|
|
TilesReused = region.TilesReused,
|
|
CreatedAt = region.CreatedAt,
|
|
UpdatedAt = region.UpdatedAt
|
|
};
|
|
}
|
|
}
|
|
|