mirror of
https://github.com/azaion/satellite-provider.git
synced 2026-06-21 08:21:14 +00:00
[AZ-367] Refactor C14: extract shared TileGridStitcher
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -4,11 +4,11 @@ using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using SatelliteProvider.Common.Configs;
|
||||
using SatelliteProvider.Common.Imaging;
|
||||
using SatelliteProvider.Common.Utils;
|
||||
using SatelliteProvider.DataAccess.Repositories;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using SixLabors.ImageSharp.Processing;
|
||||
|
||||
namespace SatelliteProvider.Services.RouteManagement;
|
||||
|
||||
@@ -458,103 +458,82 @@ public class RouteProcessingService : BackgroundService
|
||||
|
||||
const int tileSizePixels = 256;
|
||||
|
||||
var tileCoords = tiles.Select(t =>
|
||||
{
|
||||
var (tileX, tileY) = ExtractTileCoordinatesFromFilename(t.FilePath);
|
||||
return new
|
||||
var placements = tiles
|
||||
.Select(t =>
|
||||
{
|
||||
t.Latitude,
|
||||
t.Longitude,
|
||||
t.FilePath,
|
||||
TileX = tileX,
|
||||
TileY = tileY
|
||||
};
|
||||
}).Where(t => t.TileX >= 0 && t.TileY >= 0).ToList();
|
||||
|
||||
var minX = tileCoords.Min(t => t.TileX);
|
||||
var maxX = tileCoords.Max(t => t.TileX);
|
||||
var minY = tileCoords.Min(t => t.TileY);
|
||||
var maxY = tileCoords.Max(t => t.TileY);
|
||||
|
||||
var gridWidth = maxX - minX + 1;
|
||||
var gridHeight = maxY - minY + 1;
|
||||
var imageWidth = gridWidth * tileSizePixels;
|
||||
var imageHeight = gridHeight * tileSizePixels;
|
||||
|
||||
using var stitchedImage = new Image<Rgb24>(imageWidth, imageHeight);
|
||||
stitchedImage.Mutate(ctx => ctx.BackgroundColor(Color.Black));
|
||||
|
||||
var uniqueTileCoords = tileCoords
|
||||
.GroupBy(t => $"{t.TileX}_{t.TileY}")
|
||||
.Select(g => g.First())
|
||||
.OrderBy(t => t.TileY)
|
||||
.ThenBy(t => t.TileX)
|
||||
var (tileX, tileY) = ExtractTileCoordinatesFromFilename(t.FilePath);
|
||||
return new TilePlacement(tileX, tileY, t.FilePath);
|
||||
})
|
||||
.Where(p => p.TileX >= 0 && p.TileY >= 0)
|
||||
.ToList();
|
||||
|
||||
int placedTiles = 0;
|
||||
int missingTiles = 0;
|
||||
|
||||
foreach (var tile in uniqueTileCoords)
|
||||
if (placements.Count == 0)
|
||||
{
|
||||
var destX = (tile.TileX - minX) * tileSizePixels;
|
||||
var destY = (tile.TileY - minY) * tileSizePixels;
|
||||
_logger.LogWarning("No tiles with extractable coordinates to stitch for route map");
|
||||
return;
|
||||
}
|
||||
|
||||
if (File.Exists(tile.FilePath))
|
||||
var stitcher = new TileGridStitcher();
|
||||
var result = await stitcher.StitchAsync(
|
||||
placements,
|
||||
tileSizePixels,
|
||||
deduplicateByTileCoords: true,
|
||||
swallowTileLoadErrors: true,
|
||||
cancellationToken);
|
||||
|
||||
using var stitchedImage = result.Image;
|
||||
|
||||
foreach (var missing in result.MissingTiles)
|
||||
{
|
||||
if (missing.Reason == MissingTileReason.LoadFailed)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var tileImage = await Image.LoadAsync<Rgb24>(tile.FilePath, cancellationToken);
|
||||
|
||||
stitchedImage.Mutate(ctx => ctx.DrawImage(tileImage, new Point(destX, destY), 1f));
|
||||
placedTiles++;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Failed to load tile at {FilePath}, leaving black", tile.FilePath);
|
||||
missingTiles++;
|
||||
}
|
||||
_logger.LogWarning(missing.Error, "Failed to load tile at {FilePath}, leaving black", missing.Tile.FilePath);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning("Tile file not found: {FilePath}, leaving black", tile.FilePath);
|
||||
missingTiles++;
|
||||
_logger.LogWarning("Tile file not found: {FilePath}, leaving black", missing.Tile.FilePath);
|
||||
}
|
||||
}
|
||||
|
||||
if (geofencePolygonBounds.Count > 0)
|
||||
var minX = result.MinX;
|
||||
var minY = result.MinY;
|
||||
var imageWidth = result.ImageWidth;
|
||||
var imageHeight = result.ImageHeight;
|
||||
var yellow = new Rgb24(255, 255, 0);
|
||||
var red = new Rgb24(255, 0, 0);
|
||||
|
||||
foreach (var (geoMinX, geoMinY, geoMaxX, geoMaxY) in geofencePolygonBounds)
|
||||
{
|
||||
for (int i = 0; i < geofencePolygonBounds.Count; i++)
|
||||
var x1 = (geoMinX - minX) * tileSizePixels;
|
||||
var y1 = (geoMinY - minY + 1) * tileSizePixels;
|
||||
var x2 = (geoMaxX - minX + 2) * tileSizePixels - 1;
|
||||
var y2 = (geoMaxY - minY + 1) * tileSizePixels - 1;
|
||||
|
||||
x1 = Math.Max(0, Math.Min(x1, imageWidth - 1));
|
||||
y1 = Math.Max(0, Math.Min(y1, imageHeight - 1));
|
||||
x2 = Math.Max(0, Math.Min(x2, imageWidth - 1));
|
||||
y2 = Math.Max(0, Math.Min(y2, imageHeight - 1));
|
||||
|
||||
if (x1 >= 0 && y1 >= 0 && x2 < imageWidth && y2 < imageHeight && x2 > x1 && y2 > y1)
|
||||
{
|
||||
var (geoMinX, geoMinY, geoMaxX, geoMaxY) = geofencePolygonBounds[i];
|
||||
|
||||
var x1 = (geoMinX - minX) * tileSizePixels;
|
||||
var y1 = (geoMinY - minY + 1) * tileSizePixels;
|
||||
var x2 = (geoMaxX - minX + 2) * tileSizePixels - 1;
|
||||
var y2 = (geoMaxY - minY + 1) * tileSizePixels - 1;
|
||||
|
||||
x1 = Math.Max(0, Math.Min(x1, imageWidth - 1));
|
||||
y1 = Math.Max(0, Math.Min(y1, imageHeight - 1));
|
||||
x2 = Math.Max(0, Math.Min(x2, imageWidth - 1));
|
||||
y2 = Math.Max(0, Math.Min(y2, imageHeight - 1));
|
||||
|
||||
if (x1 >= 0 && y1 >= 0 && x2 < imageWidth && y2 < imageHeight && x2 > x1 && y2 > y1)
|
||||
{
|
||||
DrawRectangleBorder(stitchedImage, x1, y1, x2, y2, new Rgb24(255, 255, 0));
|
||||
}
|
||||
stitcher.DrawRectangleBorder(
|
||||
stitchedImage,
|
||||
new Rectangle(x1, y1, x2 - x1 + 1, y2 - y1 + 1),
|
||||
yellow);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
foreach (var point in routePoints)
|
||||
{
|
||||
var geoPoint = new Common.DTO.GeoPoint { Lat = point.Latitude, Lon = point.Longitude };
|
||||
var (tileX, tileY) = GeoUtils.WorldToTilePos(geoPoint, zoomLevel);
|
||||
|
||||
|
||||
var pixelX = (tileX - minX) * tileSizePixels + tileSizePixels / 2;
|
||||
var pixelY = (tileY - minY) * tileSizePixels + tileSizePixels / 2;
|
||||
|
||||
|
||||
if (pixelX >= 0 && pixelX < imageWidth && pixelY >= 0 && pixelY < imageHeight)
|
||||
{
|
||||
DrawCross(stitchedImage, pixelX, pixelY, new Rgb24(255, 0, 0), 50);
|
||||
stitcher.DrawCross(stitchedImage, new Point(pixelX, pixelY), red, 50);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -599,62 +578,6 @@ public class RouteProcessingService : BackgroundService
|
||||
return (-1, -1);
|
||||
}
|
||||
|
||||
private static void DrawRectangleBorder(Image<Rgb24> image, int x1, int y1, int x2, int y2, Rgb24 color)
|
||||
{
|
||||
const int thickness = 5;
|
||||
|
||||
for (int t = 0; t < thickness; t++)
|
||||
{
|
||||
for (int x = x1; x <= x2; x++)
|
||||
{
|
||||
int topY = y1 + t;
|
||||
int bottomY = y2 - t;
|
||||
if (x >= 0 && x < image.Width && topY >= 0 && topY < image.Height)
|
||||
image[x, topY] = color;
|
||||
if (x >= 0 && x < image.Width && bottomY >= 0 && bottomY < image.Height)
|
||||
image[x, bottomY] = color;
|
||||
}
|
||||
|
||||
for (int y = y1; y <= y2; y++)
|
||||
{
|
||||
int leftX = x1 + t;
|
||||
int rightX = x2 - t;
|
||||
if (leftX >= 0 && leftX < image.Width && y >= 0 && y < image.Height)
|
||||
image[leftX, y] = color;
|
||||
if (rightX >= 0 && rightX < image.Width && y >= 0 && y < image.Height)
|
||||
image[rightX, y] = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void DrawCross(Image<Rgb24> image, int centerX, int centerY, Rgb24 color, int armLength)
|
||||
{
|
||||
const int thickness = 10;
|
||||
int halfThickness = thickness / 2;
|
||||
|
||||
for (int dx = -armLength; dx <= armLength; dx++)
|
||||
{
|
||||
for (int t = -halfThickness; t <= halfThickness; t++)
|
||||
{
|
||||
int x = centerX + dx;
|
||||
int y = centerY + t;
|
||||
if (x >= 0 && x < image.Width && y >= 0 && y < image.Height)
|
||||
image[x, y] = color;
|
||||
}
|
||||
}
|
||||
|
||||
for (int dy = -armLength; dy <= armLength; dy++)
|
||||
{
|
||||
for (int t = -halfThickness; t <= halfThickness; t++)
|
||||
{
|
||||
int x = centerX + t;
|
||||
int y = centerY + dy;
|
||||
if (x >= 0 && x < image.Width && y >= 0 && y < image.Height)
|
||||
image[x, y] = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Task CreateTilesZipAsync(
|
||||
string zipFilePath,
|
||||
IEnumerable<TileInfo> tiles,
|
||||
|
||||
Reference in New Issue
Block a user