using Dapper; using Microsoft.Extensions.Logging; using Npgsql; using SatelliteProvider.DataAccess.Models; namespace SatelliteProvider.DataAccess.Repositories; public class TileRepository : ITileRepository { private readonly string _connectionString; private readonly ILogger _logger; public TileRepository(string connectionString, ILogger logger) { _connectionString = connectionString; _logger = logger; } public async Task GetByIdAsync(Guid id) { using var connection = new NpgsqlConnection(_connectionString); const string sql = @" SELECT id, tile_zoom as TileZoom, tile_x as TileX, tile_y as TileY, latitude, longitude, tile_size_meters as TileSizeMeters, tile_size_pixels as TileSizePixels, image_type as ImageType, maps_version as MapsVersion, version, file_path as FilePath, created_at as CreatedAt, updated_at as UpdatedAt FROM tiles WHERE id = @Id"; return await connection.QuerySingleOrDefaultAsync(sql, new { Id = id }); } public async Task GetByTileCoordinatesAsync(int tileZoom, int tileX, int tileY) { using var connection = new NpgsqlConnection(_connectionString); const string sql = @" SELECT id, tile_zoom as TileZoom, tile_x as TileX, tile_y as TileY, latitude, longitude, tile_size_meters as TileSizeMeters, tile_size_pixels as TileSizePixels, image_type as ImageType, maps_version as MapsVersion, version, file_path as FilePath, created_at as CreatedAt, updated_at as UpdatedAt FROM tiles WHERE tile_zoom = @TileZoom AND tile_x = @TileX AND tile_y = @TileY ORDER BY version DESC LIMIT 1"; return await connection.QuerySingleOrDefaultAsync(sql, new { TileZoom = tileZoom, TileX = tileX, TileY = tileY }); } public async Task FindExistingTileAsync(double latitude, double longitude, double tileSizeMeters, int zoomLevel, int version) { using var connection = new NpgsqlConnection(_connectionString); const string sql = @" SELECT id, tile_zoom as TileZoom, tile_x as TileX, tile_y as TileY, latitude, longitude, tile_size_meters as TileSizeMeters, tile_size_pixels as TileSizePixels, image_type as ImageType, maps_version as MapsVersion, version, file_path as FilePath, created_at as CreatedAt, updated_at as UpdatedAt FROM tiles WHERE ABS(latitude - @Latitude) < 0.0001 AND ABS(longitude - @Longitude) < 0.0001 AND ABS(tile_size_meters - @TileSizeMeters) < 1 AND tile_zoom = @TileZoom AND version = @Version LIMIT 1"; return await connection.QuerySingleOrDefaultAsync(sql, new { Latitude = latitude, Longitude = longitude, TileSizeMeters = tileSizeMeters, TileZoom = zoomLevel, Version = version }); } public async Task> GetTilesByRegionAsync(double latitude, double longitude, double sizeMeters, int zoomLevel) { using var connection = new NpgsqlConnection(_connectionString); const double EARTH_CIRCUMFERENCE_METERS = 40075016.686; const int TILE_SIZE_PIXELS = 256; var latRad = latitude * Math.PI / 180.0; var metersPerPixel = (EARTH_CIRCUMFERENCE_METERS * Math.Cos(latRad)) / (Math.Pow(2, zoomLevel) * TILE_SIZE_PIXELS); var tileSizeMeters = metersPerPixel * TILE_SIZE_PIXELS; var expandedSizeMeters = sizeMeters + (tileSizeMeters * 2); var latRange = expandedSizeMeters / 111000.0; var lonRange = expandedSizeMeters / (111000.0 * Math.Cos(latitude * Math.PI / 180.0)); const string sql = @" SELECT id, tile_zoom as TileZoom, tile_x as TileX, tile_y as TileY, latitude, longitude, tile_size_meters as TileSizeMeters, tile_size_pixels as TileSizePixels, image_type as ImageType, maps_version as MapsVersion, version, file_path as FilePath, created_at as CreatedAt, updated_at as UpdatedAt FROM tiles WHERE latitude BETWEEN @MinLat AND @MaxLat AND longitude BETWEEN @MinLon AND @MaxLon AND tile_zoom = @TileZoom ORDER BY version DESC, latitude DESC, longitude ASC"; return await connection.QueryAsync(sql, new { MinLat = latitude - latRange / 2, MaxLat = latitude + latRange / 2, MinLon = longitude - lonRange / 2, MaxLon = longitude + lonRange / 2, TileZoom = zoomLevel }); } public async Task InsertAsync(TileEntity tile) { using var connection = new NpgsqlConnection(_connectionString); const string sql = @" INSERT INTO tiles (id, tile_zoom, tile_x, tile_y, latitude, longitude, tile_size_meters, tile_size_pixels, image_type, maps_version, version, file_path, created_at, updated_at) VALUES (@Id, @TileZoom, @TileX, @TileY, @Latitude, @Longitude, @TileSizeMeters, @TileSizePixels, @ImageType, @MapsVersion, @Version, @FilePath, @CreatedAt, @UpdatedAt) ON CONFLICT (latitude, longitude, tile_zoom, tile_size_meters, version) DO UPDATE SET file_path = EXCLUDED.file_path, tile_x = EXCLUDED.tile_x, tile_y = EXCLUDED.tile_y, updated_at = EXCLUDED.updated_at RETURNING id"; return await connection.ExecuteScalarAsync(sql, tile); } public async Task UpdateAsync(TileEntity tile) { using var connection = new NpgsqlConnection(_connectionString); const string sql = @" UPDATE tiles SET tile_zoom = @TileZoom, tile_x = @TileX, tile_y = @TileY, latitude = @Latitude, longitude = @Longitude, tile_size_meters = @TileSizeMeters, tile_size_pixels = @TileSizePixels, image_type = @ImageType, maps_version = @MapsVersion, version = @Version, file_path = @FilePath, updated_at = @UpdatedAt WHERE id = @Id"; return await connection.ExecuteAsync(sql, tile); } public async Task DeleteAsync(Guid id) { using var connection = new NpgsqlConnection(_connectionString); const string sql = "DELETE FROM tiles WHERE id = @Id"; return await connection.ExecuteAsync(sql, new { Id = id }); } }