mirror of
https://github.com/azaion/satellite-provider.git
synced 2026-04-23 03:26:39 +00:00
better geo fences and points on the map
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE route_regions ADD COLUMN geofence_polygon_index INTEGER;
|
||||
|
||||
@@ -10,9 +10,10 @@ public interface IRouteRepository
|
||||
Task InsertRoutePointsAsync(IEnumerable<RoutePointEntity> points);
|
||||
Task<int> UpdateRouteAsync(RouteEntity route);
|
||||
Task<int> DeleteRouteAsync(Guid id);
|
||||
Task LinkRouteToRegionAsync(Guid routeId, Guid regionId, bool isGeofence = false);
|
||||
Task LinkRouteToRegionAsync(Guid routeId, Guid regionId, bool isGeofence = false, int? geofencePolygonIndex = null);
|
||||
Task<IEnumerable<Guid>> GetRegionIdsByRouteAsync(Guid routeId);
|
||||
Task<IEnumerable<Guid>> GetGeofenceRegionIdsByRouteAsync(Guid routeId);
|
||||
Task<Dictionary<int, List<Guid>>> GetGeofenceRegionsByPolygonAsync(Guid routeId);
|
||||
Task<IEnumerable<RouteEntity>> GetRoutesWithPendingMapsAsync();
|
||||
}
|
||||
|
||||
|
||||
@@ -121,15 +121,15 @@ public class RouteRepository : IRouteRepository
|
||||
return await connection.ExecuteAsync(sql, new { Id = id });
|
||||
}
|
||||
|
||||
public async Task LinkRouteToRegionAsync(Guid routeId, Guid regionId, bool isGeofence = false)
|
||||
public async Task LinkRouteToRegionAsync(Guid routeId, Guid regionId, bool isGeofence = false, int? geofencePolygonIndex = null)
|
||||
{
|
||||
using var connection = new NpgsqlConnection(_connectionString);
|
||||
const string sql = @"
|
||||
INSERT INTO route_regions (route_id, region_id, is_geofence, created_at)
|
||||
VALUES (@RouteId, @RegionId, @IsGeofence, @CreatedAt)
|
||||
INSERT INTO route_regions (route_id, region_id, is_geofence, geofence_polygon_index, created_at)
|
||||
VALUES (@RouteId, @RegionId, @IsGeofence, @GeofencePolygonIndex, @CreatedAt)
|
||||
ON CONFLICT (route_id, region_id) DO NOTHING";
|
||||
|
||||
await connection.ExecuteAsync(sql, new { RouteId = routeId, RegionId = regionId, IsGeofence = isGeofence, CreatedAt = DateTime.UtcNow });
|
||||
await connection.ExecuteAsync(sql, new { RouteId = routeId, RegionId = regionId, IsGeofence = isGeofence, GeofencePolygonIndex = geofencePolygonIndex, CreatedAt = DateTime.UtcNow });
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Guid>> GetRegionIdsByRouteAsync(Guid routeId)
|
||||
@@ -169,5 +169,29 @@ public class RouteRepository : IRouteRepository
|
||||
|
||||
return await connection.QueryAsync<RouteEntity>(sql);
|
||||
}
|
||||
|
||||
public async Task<Dictionary<int, List<Guid>>> GetGeofenceRegionsByPolygonAsync(Guid routeId)
|
||||
{
|
||||
using var connection = new NpgsqlConnection(_connectionString);
|
||||
const string sql = @"
|
||||
SELECT region_id, geofence_polygon_index
|
||||
FROM route_regions
|
||||
WHERE route_id = @RouteId AND is_geofence = true AND geofence_polygon_index IS NOT NULL
|
||||
ORDER BY geofence_polygon_index";
|
||||
|
||||
var results = await connection.QueryAsync<(Guid RegionId, int PolygonIndex)>(sql, new { RouteId = routeId });
|
||||
|
||||
var grouped = new Dictionary<int, List<Guid>>();
|
||||
foreach (var (regionId, polygonIndex) in results)
|
||||
{
|
||||
if (!grouped.ContainsKey(polygonIndex))
|
||||
{
|
||||
grouped[polygonIndex] = new List<Guid>();
|
||||
}
|
||||
grouped[polygonIndex].Add(regionId);
|
||||
}
|
||||
|
||||
return grouped;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -196,7 +196,7 @@ public class RouteProcessingService : BackgroundService
|
||||
var allRegionIdsForStitching = routeRegionIds.Concat(completedGeofenceRegions.Select(r => r.Id)).Distinct();
|
||||
var geofenceRegionIdsForBorders = completedGeofenceRegions.Select(r => r.Id).ToList();
|
||||
|
||||
await GenerateRouteMapsAsync(routeId, route, allRegionIdsForStitching, geofenceRegionIdsForBorders, cancellationToken);
|
||||
await GenerateRouteMapsAsync(routeId, route, allRegionIdsForStitching, geofenceRegionIdsForBorders, routePointsList, cancellationToken);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -246,6 +246,7 @@ public class RouteProcessingService : BackgroundService
|
||||
DataAccess.Models.RouteEntity route,
|
||||
IEnumerable<Guid> regionIds,
|
||||
List<Guid> geofenceRegionIds,
|
||||
List<DataAccess.Models.RoutePointEntity> routePoints,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
@@ -326,11 +327,15 @@ public class RouteProcessingService : BackgroundService
|
||||
string? stitchedImagePath = null;
|
||||
if (route.RequestMaps)
|
||||
{
|
||||
int? minX = null, minY = null, maxX = null, maxY = null;
|
||||
var geofencePolygonBounds = new List<(int MinX, int MinY, int MaxX, int MaxY)>();
|
||||
|
||||
if (geofenceRegionIds.Count > 0)
|
||||
var geofencesByPolygon = await _routeRepository.GetGeofenceRegionsByPolygonAsync(routeId);
|
||||
|
||||
foreach (var (polygonIndex, polygonRegionIds) in geofencesByPolygon.OrderBy(kvp => kvp.Key))
|
||||
{
|
||||
foreach (var geofenceId in geofenceRegionIds)
|
||||
int? minX = null, minY = null, maxX = null, maxY = null;
|
||||
|
||||
foreach (var geofenceId in polygonRegionIds)
|
||||
{
|
||||
var region = await _regionRepository.GetByIdAsync(geofenceId);
|
||||
if (region != null && !string.IsNullOrEmpty(region.CsvFilePath) && File.Exists(region.CsvFilePath))
|
||||
@@ -357,18 +362,15 @@ public class RouteProcessingService : BackgroundService
|
||||
|
||||
if (minX.HasValue && minY.HasValue && maxX.HasValue && maxY.HasValue)
|
||||
{
|
||||
_logger.LogInformation("Route {RouteId}: Combined geofence tile bounds: X=[{MinX}..{MaxX}], Y=[{MinY}..{MaxY}]",
|
||||
routeId, minX.Value, maxX.Value, minY.Value, maxY.Value);
|
||||
geofencePolygonBounds.Add((minX.Value, minY.Value, maxX.Value, maxY.Value));
|
||||
_logger.LogInformation("Route {RouteId}: Polygon {PolygonIndex} tile bounds: X=[{MinX}..{MaxX}], Y=[{MinY}..{MaxY}]",
|
||||
routeId, polygonIndex, minX.Value, maxX.Value, minY.Value, maxY.Value);
|
||||
}
|
||||
}
|
||||
|
||||
stitchedImagePath = Path.Combine(readyDir, $"route_{routeId}_stitched.jpg");
|
||||
|
||||
var geofenceBounds = (minX.HasValue && minY.HasValue && maxX.HasValue && maxY.HasValue)
|
||||
? (minX.Value, minY.Value, maxX.Value, maxY.Value)
|
||||
: ((int, int, int, int)?)null;
|
||||
|
||||
await StitchRouteTilesAsync(allTiles.Values.ToList(), stitchedImagePath, route.ZoomLevel, geofenceBounds, cancellationToken);
|
||||
await StitchRouteTilesAsync(allTiles.Values.ToList(), stitchedImagePath, route.ZoomLevel, geofencePolygonBounds, routePoints, cancellationToken);
|
||||
}
|
||||
|
||||
var summaryPath = Path.Combine(readyDir, $"route_{routeId}_summary.txt");
|
||||
@@ -455,7 +457,8 @@ public class RouteProcessingService : BackgroundService
|
||||
List<TileInfo> tiles,
|
||||
string outputPath,
|
||||
int zoomLevel,
|
||||
(int MinX, int MinY, int MaxX, int MaxY)? geofenceBounds,
|
||||
List<(int MinX, int MinY, int MaxX, int MaxY)> geofencePolygonBounds,
|
||||
List<DataAccess.Models.RoutePointEntity> routePoints,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (tiles.Count == 0)
|
||||
@@ -554,44 +557,66 @@ public class RouteProcessingService : BackgroundService
|
||||
}
|
||||
}
|
||||
|
||||
if (geofenceBounds.HasValue)
|
||||
if (geofencePolygonBounds.Count > 0)
|
||||
{
|
||||
var (geoMinX, geoMinY, geoMaxX, geoMaxY) = geofenceBounds.Value;
|
||||
_logger.LogInformation("Drawing {Count} geofence polygon borders on image {Width}x{Height} (grid: minX={MinX}, minY={MinY})",
|
||||
geofencePolygonBounds.Count, imageWidth, imageHeight, minX, minY);
|
||||
|
||||
_logger.LogInformation("Drawing geofence border on image {Width}x{Height} (grid: minX={MinX}, minY={MinY})",
|
||||
imageWidth, imageHeight, minX, minY);
|
||||
_logger.LogInformation("Geofence tile range - X=[{MinX}..{MaxX}], Y=[{MinY}..{MaxY}]",
|
||||
geoMinX, geoMaxX, geoMinY, geoMaxY);
|
||||
|
||||
var x1 = (geoMinX - minX) * tileSizePixels;
|
||||
var y1 = (geoMinY - minY) * tileSizePixels;
|
||||
var x2 = (geoMaxX - minX + 1) * tileSizePixels - 1;
|
||||
var y2 = (geoMaxY - minY + 1) * tileSizePixels - 1;
|
||||
|
||||
_logger.LogInformation("Geofence pixel coords before clipping - ({X1},{Y1}) to ({X2},{Y2})",
|
||||
x1, y1, x2, y2);
|
||||
|
||||
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)
|
||||
for (int i = 0; i < geofencePolygonBounds.Count; i++)
|
||||
{
|
||||
_logger.LogInformation("Drawing geofence border at pixel coords ({X1},{Y1}) to ({X2},{Y2})",
|
||||
x1, y1, x2, y2);
|
||||
var (geoMinX, geoMinY, geoMaxX, geoMaxY) = geofencePolygonBounds[i];
|
||||
|
||||
DrawRectangleBorder(stitchedImage, x1, y1, x2, y2, new Rgb24(255, 255, 0));
|
||||
_logger.LogInformation("Polygon {Index}: Tile range - X=[{MinX}..{MaxX}], Y=[{MinY}..{MaxY}]",
|
||||
i, geoMinX, geoMaxX, geoMinY, geoMaxY);
|
||||
|
||||
_logger.LogInformation("Successfully drew geofence border");
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning("Geofence border out of bounds or invalid - ({X1},{Y1}) to ({X2},{Y2}), image size: {Width}x{Height}",
|
||||
x1, y1, x2, y2, imageWidth, imageHeight);
|
||||
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;
|
||||
|
||||
_logger.LogInformation("Polygon {Index}: Pixel coords before clipping - ({X1},{Y1}) to ({X2},{Y2})",
|
||||
i, x1, y1, x2, y2);
|
||||
|
||||
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)
|
||||
{
|
||||
_logger.LogInformation("Polygon {Index}: Drawing border at pixel coords ({X1},{Y1}) to ({X2},{Y2})",
|
||||
i, x1, y1, x2, y2);
|
||||
|
||||
DrawRectangleBorder(stitchedImage, x1, y1, x2, y2, new Rgb24(255, 255, 0));
|
||||
|
||||
_logger.LogInformation("Polygon {Index}: Successfully drew geofence border", i);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning("Polygon {Index}: Border out of bounds or invalid - ({X1},{Y1}) to ({X2},{Y2}), image size: {Width}x{Height}",
|
||||
i, x1, y1, x2, y2, imageWidth, imageHeight);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_logger.LogInformation("Drawing {Count} route point crosses on map", routePoints.Count);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
_logger.LogInformation("Finished drawing route point crosses");
|
||||
|
||||
await stitchedImage.SaveAsJpegAsync(outputPath, cancellationToken);
|
||||
|
||||
var totalPossibleTiles = gridWidth * gridHeight;
|
||||
@@ -702,6 +727,34 @@ public class RouteProcessingService : BackgroundService
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class TileInfo
|
||||
|
||||
@@ -166,8 +166,10 @@ public class RouteService : IRouteService
|
||||
_logger.LogInformation("Route {RouteId}: Processing {GeofenceCount} geofence polygons",
|
||||
request.Id, request.Geofences.Polygons.Count);
|
||||
|
||||
foreach (var polygon in request.Geofences.Polygons)
|
||||
for (int polygonIndex = 0; polygonIndex < request.Geofences.Polygons.Count; polygonIndex++)
|
||||
{
|
||||
var polygon = request.Geofences.Polygons[polygonIndex];
|
||||
|
||||
if (polygon.NorthWest is null || polygon.SouthEast is null)
|
||||
{
|
||||
throw new ArgumentException("Geofence polygon coordinates are required");
|
||||
@@ -194,15 +196,15 @@ public class RouteService : IRouteService
|
||||
|
||||
var geofenceRegions = CreateGeofenceRegionGrid(polygon.NorthWest, polygon.SouthEast, request.RegionSizeMeters);
|
||||
|
||||
_logger.LogInformation("Route {RouteId}: Created grid of {Count} regions to cover geofence area",
|
||||
request.Id, geofenceRegions.Count);
|
||||
_logger.LogInformation("Route {RouteId}: Polygon {PolygonIndex} - Created grid of {Count} regions to cover geofence area",
|
||||
request.Id, polygonIndex, geofenceRegions.Count);
|
||||
|
||||
foreach (var geofencePoint in geofenceRegions)
|
||||
{
|
||||
var geofenceRegionId = Guid.NewGuid();
|
||||
|
||||
_logger.LogInformation("Route {RouteId}: Requesting geofence region {RegionId} at ({Lat}, {Lon}) with size {Size}m",
|
||||
request.Id, geofenceRegionId, geofencePoint.Lat, geofencePoint.Lon, request.RegionSizeMeters);
|
||||
_logger.LogInformation("Route {RouteId}: Polygon {PolygonIndex} - Requesting geofence region {RegionId} at ({Lat}, {Lon}) with size {Size}m",
|
||||
request.Id, polygonIndex, geofenceRegionId, geofencePoint.Lat, geofencePoint.Lon, request.RegionSizeMeters);
|
||||
|
||||
await _regionService.RequestRegionAsync(
|
||||
geofenceRegionId,
|
||||
@@ -212,7 +214,7 @@ public class RouteService : IRouteService
|
||||
request.ZoomLevel,
|
||||
stitchTiles: false);
|
||||
|
||||
await _routeRepository.LinkRouteToRegionAsync(request.Id, geofenceRegionId, isGeofence: true);
|
||||
await _routeRepository.LinkRouteToRegionAsync(request.Id, geofenceRegionId, isGeofence: true, geofencePolygonIndex: polygonIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user