using Microsoft.Extensions.Logging; using SatelliteProvider.Common.DTO; using SatelliteProvider.Common.Interfaces; using SatelliteProvider.DataAccess.Models; using SatelliteProvider.DataAccess.Repositories; namespace SatelliteProvider.Services; public class TileService : ITileService { private readonly GoogleMapsDownloaderV2 _downloader; private readonly ITileRepository _tileRepository; private readonly ILogger _logger; public TileService( GoogleMapsDownloaderV2 downloader, ITileRepository tileRepository, ILogger logger) { _downloader = downloader; _tileRepository = tileRepository; _logger = logger; } public async Task> DownloadAndStoreTilesAsync( double latitude, double longitude, double sizeMeters, int zoomLevel, CancellationToken cancellationToken = default) { var currentVersion = DateTime.UtcNow.Year; var existingTiles = await _tileRepository.GetTilesByRegionAsync(latitude, longitude, sizeMeters, zoomLevel); var existingTilesList = existingTiles.Where(t => t.Version == currentVersion).ToList(); _logger.LogInformation("Found {Count} existing tiles for region (version {Version})", existingTilesList.Count, currentVersion); if (existingTilesList.Any()) { _logger.LogInformation("Existing tiles in DB:"); foreach (var et in existingTilesList) { _logger.LogInformation(" DB Tile: Lat={Lat:F12}, Lon={Lon:F12}, Zoom={Zoom}", et.Latitude, et.Longitude, et.ZoomLevel); } } var centerPoint = new GeoPoint(latitude, longitude); var downloadedTiles = await _downloader.GetTilesWithMetadataAsync(centerPoint, sizeMeters / 2, zoomLevel, cancellationToken); _logger.LogInformation("Downloaded {Count} tiles from Google:", downloadedTiles.Count); foreach (var dt in downloadedTiles) { _logger.LogInformation(" Downloaded: Lat={Lat:F12}, Lon={Lon:F12}, Zoom={Zoom}", dt.CenterLatitude, dt.CenterLongitude, dt.ZoomLevel); } var result = new List(); int reusedCount = 0; int downloadedCount = 0; foreach (var downloadedTile in downloadedTiles) { var existingTile = existingTilesList.FirstOrDefault(t => Math.Abs(t.Latitude - downloadedTile.CenterLatitude) < 0.0001 && Math.Abs(t.Longitude - downloadedTile.CenterLongitude) < 0.0001 && t.ZoomLevel == downloadedTile.ZoomLevel); if (existingTile != null) { reusedCount++; _logger.LogInformation("REUSED tile at ({Lat:F12}, {Lon:F12}) - matched DB tile ID {Id}", downloadedTile.CenterLatitude, downloadedTile.CenterLongitude, existingTile.Id); result.Add(MapToMetadata(existingTile)); } else { downloadedCount++; _logger.LogInformation("NEW tile needed at ({Lat:F12}, {Lon:F12}) - no match in DB", downloadedTile.CenterLatitude, downloadedTile.CenterLongitude); var closestTile = existingTilesList .Select(t => new { Tile = t, LatDiff = Math.Abs(t.Latitude - downloadedTile.CenterLatitude), LonDiff = Math.Abs(t.Longitude - downloadedTile.CenterLongitude) }) .OrderBy(x => x.LatDiff + x.LonDiff) .FirstOrDefault(); if (closestTile != null) { _logger.LogInformation(" Closest DB tile: Lat={Lat:F12} (diff={LatDiff:F12}), Lon={Lon:F12} (diff={LonDiff:F12})", closestTile.Tile.Latitude, closestTile.LatDiff, closestTile.Tile.Longitude, closestTile.LonDiff); } var now = DateTime.UtcNow; var tileEntity = new TileEntity { Id = Guid.NewGuid(), ZoomLevel = downloadedTile.ZoomLevel, Latitude = downloadedTile.CenterLatitude, Longitude = downloadedTile.CenterLongitude, TileSizeMeters = downloadedTile.TileSizeMeters, TileSizePixels = 256, ImageType = "jpg", MapsVersion = $"downloaded_{now:yyyy-MM-dd}", Version = currentVersion, FilePath = downloadedTile.FilePath, CreatedAt = now, UpdatedAt = now }; await _tileRepository.InsertAsync(tileEntity); _logger.LogInformation("Saved new tile {Id} at ({Lat:F12}, {Lon:F12}) version {Version}", tileEntity.Id, tileEntity.Latitude, tileEntity.Longitude, currentVersion); result.Add(MapToMetadata(tileEntity)); } } _logger.LogInformation("Tile processing summary: {Reused} reused, {Downloaded} new", reusedCount, downloadedCount); return result; } public async Task GetTileAsync(Guid id) { var tile = await _tileRepository.GetByIdAsync(id); return tile != null ? MapToMetadata(tile) : null; } public async Task> GetTilesByRegionAsync( double latitude, double longitude, double sizeMeters, int zoomLevel) { var tiles = await _tileRepository.GetTilesByRegionAsync(latitude, longitude, sizeMeters, zoomLevel); return tiles.Select(MapToMetadata); } private static TileMetadata MapToMetadata(TileEntity entity) { return new TileMetadata { Id = entity.Id, ZoomLevel = entity.ZoomLevel, Latitude = entity.Latitude, Longitude = entity.Longitude, TileSizeMeters = entity.TileSizeMeters, TileSizePixels = entity.TileSizePixels, ImageType = entity.ImageType, MapsVersion = entity.MapsVersion, Version = entity.Version, FilePath = entity.FilePath, CreatedAt = entity.CreatedAt, UpdatedAt = entity.UpdatedAt }; } }