[AZ-367] Refactor C14: extract shared TileGridStitcher

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Oleksandr Bezdieniezhnykh
2026-05-11 02:55:25 +03:00
parent 23d513b24c
commit 10d31b4c1c
6 changed files with 850 additions and 177 deletions
@@ -3,13 +3,13 @@ using Microsoft.Extensions.Options;
using SatelliteProvider.Common.Configs;
using SatelliteProvider.Common.DTO;
using SatelliteProvider.Common.Exceptions;
using SatelliteProvider.Common.Imaging;
using SatelliteProvider.Common.Interfaces;
using SatelliteProvider.Common.Utils;
using SatelliteProvider.DataAccess.Models;
using SatelliteProvider.DataAccess.Repositories;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
namespace SatelliteProvider.Services.RegionProcessing;
@@ -224,69 +224,56 @@ public class RegionService : IRegionService
}
var tileSizePixels = tiles.First().TileSizePixels;
var tileCoords = tiles.Select(t =>
var placements = tiles.Select(t =>
{
var (x, y) = GeoUtils.WorldToTilePos(new GeoPoint(t.Latitude, t.Longitude), zoomLevel);
return (x, y, t.FilePath);
}).ToList();
return new TilePlacement(x, y, t.FilePath);
});
var minX = tileCoords.Min(t => t.x);
var maxX = tileCoords.Max(t => t.x);
var minY = tileCoords.Min(t => t.y);
var maxY = tileCoords.Max(t => t.y);
var stitcher = new TileGridStitcher();
var result = await stitcher.StitchAsync(
placements,
tileSizePixels,
deduplicateByTileCoords: false,
swallowTileLoadErrors: false,
cancellationToken);
var gridWidth = maxX - minX + 1;
var gridHeight = maxY - minY + 1;
var imageWidth = gridWidth * tileSizePixels;
var imageHeight = gridHeight * tileSizePixels;
using var stitchedImage = result.Image;
using var stitchedImage = new Image<Rgb24>(imageWidth, imageHeight);
foreach (var (x, y, filePath) in tileCoords)
foreach (var missing in result.MissingTiles)
{
if (!File.Exists(filePath))
{
_logger.LogWarning("Tile file not found: {FilePath}", filePath);
continue;
}
using var tileImage = await Image.LoadAsync<Rgb24>(filePath, cancellationToken);
var destX = (x - minX) * tileSizePixels;
var destY = (y - minY) * tileSizePixels;
stitchedImage.Mutate(ctx => ctx.DrawImage(tileImage, new Point(destX, destY), 1f));
_logger.LogWarning("Tile file not found: {FilePath}", missing.Tile.FilePath);
}
var (centerTileX, centerTileY) = GeoUtils.WorldToTilePos(new GeoPoint(centerLatitude, centerLongitude), zoomLevel);
var n = Math.Pow(2.0, zoomLevel);
var centerTilePixelX = ((centerLongitude + 180.0) / 360.0 * n - centerTileX) * tileSizePixels;
var centerTilePixelY = ((1.0 - Math.Log(Math.Tan(centerLatitude * Math.PI / 180.0) + 1.0 / Math.Cos(centerLatitude * Math.PI / 180.0)) / Math.PI) / 2.0 * n - centerTileY) * tileSizePixels;
var crossX = (int)Math.Round((centerTileX - minX) * tileSizePixels + centerTilePixelX);
var crossY = (int)Math.Round((centerTileY - minY) * tileSizePixels + centerTilePixelY);
var crossX = (int)Math.Round((centerTileX - result.MinX) * tileSizePixels + centerTilePixelX);
var crossY = (int)Math.Round((centerTileY - result.MinY) * tileSizePixels + centerTilePixelY);
var red = new Rgb24(255, 0, 0);
stitchedImage.Mutate(ctx =>
var imageWidth = result.ImageWidth;
var imageHeight = result.ImageHeight;
for (int i = -5; i < 5; i++)
{
for (int i = -5; i < 5; i++)
var hx = crossX + i;
var vy = crossY + i;
if (hx >= 0 && hx < imageWidth && crossY >= 0 && crossY < imageHeight)
{
var hx = crossX + i;
var vy = crossY + i;
if (hx >= 0 && hx < imageWidth && crossY >= 0 && crossY < imageHeight)
{
stitchedImage[hx, crossY] = red;
}
if (crossX >= 0 && crossX < imageWidth && vy >= 0 && vy < imageHeight)
{
stitchedImage[crossX, vy] = red;
}
stitchedImage[hx, crossY] = red;
}
});
if (crossX >= 0 && crossX < imageWidth && vy >= 0 && vy < imageHeight)
{
stitchedImage[crossX, vy] = red;
}
}
await stitchedImage.SaveAsJpegAsync(outputPath, cancellationToken);