diff --git a/SatelliteProvider.DataAccess/SatelliteProvider.DataAccess.csproj b/SatelliteProvider.DataAccess/SatelliteProvider.DataAccess.csproj index 86dc555..b403f78 100644 --- a/SatelliteProvider.DataAccess/SatelliteProvider.DataAccess.csproj +++ b/SatelliteProvider.DataAccess/SatelliteProvider.DataAccess.csproj @@ -9,7 +9,7 @@ - + diff --git a/SatelliteProvider.IntegrationTests/Program.cs b/SatelliteProvider.IntegrationTests/Program.cs index fb7156d..baf92c8 100644 --- a/SatelliteProvider.IntegrationTests/Program.cs +++ b/SatelliteProvider.IntegrationTests/Program.cs @@ -37,7 +37,7 @@ class Program // await RouteTests.RunRouteWithRegionProcessingAndStitching(httpClient); await RouteTests.RunComplexRouteWithStitching(httpClient); - + await RouteTests.RunComplexRouteWithStitchingAndGeofences(httpClient); // await RouteTests.RunExtendedRouteEast(httpClient); Console.WriteLine(); diff --git a/SatelliteProvider.IntegrationTests/RouteTests.cs b/SatelliteProvider.IntegrationTests/RouteTests.cs index 099b1d9..901c173 100644 --- a/SatelliteProvider.IntegrationTests/RouteTests.cs +++ b/SatelliteProvider.IntegrationTests/RouteTests.cs @@ -289,6 +289,7 @@ public static class RouteTests Console.WriteLine("✓ Route with Region Processing and Stitching Test: PASSED"); } + public static async Task RunComplexRouteWithStitching(HttpClient httpClient) { Console.WriteLine(); @@ -296,6 +297,227 @@ public static class RouteTests Console.WriteLine("======================================================================================"); Console.WriteLine(); + var routeId = Guid.NewGuid(); + var request = new CreateRouteRequest + { + Id = routeId, + Name = "Complex Route with 10 Points + 2 Geofences", + Description = "Test route with 10 action points and 2 geofence regions for complex map stitching", + RegionSizeMeters = 300.0, + ZoomLevel = 18, + RequestMaps = true, + Points = new List + { + new() { Lat = 48.276067180586544, Lon = 37.38445758819581 }, + new() { Lat = 48.27074009522731, Lon = 37.374029159545906 }, + new() { Lat = 48.263312668696855, Lon = 37.37707614898682 }, + new() { Lat = 48.26539817051818, Lon = 37.36587524414063 }, + new() { Lat = 48.25851283439989, Lon = 37.35952377319337 }, + new() { Lat = 48.254426906081555, Lon = 37.374801635742195 }, + new() { Lat = 48.25914140977405, Lon = 37.39068031311036 }, + new() { Lat = 48.25354110233028, Lon = 37.401752471923835 }, + new() { Lat = 48.25902712391726, Lon = 37.416257858276374 }, + new() { Lat = 48.26828345053738, Lon = 37.402009963989265 } + }, + }; + + Console.WriteLine("Step 1: Creating complex route with RequestMaps=true and 2 geofences"); + Console.WriteLine($" Action Points: {request.Points.Count}"); + Console.WriteLine($" NO Geofences Regions"); + Console.WriteLine($" Region Size: {request.RegionSizeMeters}m"); + Console.WriteLine($" Zoom Level: {request.ZoomLevel}"); + Console.WriteLine($" Request Maps: {request.RequestMaps}"); + Console.WriteLine(); + Console.WriteLine("Route Path:"); + for (int i = 0; i < request.Points.Count; i++) + { + Console.WriteLine($" {i + 1}. ({request.Points[i].Lat}, {request.Points[i].Lon})"); + } + Console.WriteLine(); + + var routeResponse = await httpClient.PostAsJsonAsync("/api/satellite/route", request, JsonWriteOptions); + + if (!routeResponse.IsSuccessStatusCode) + { + var errorContent = await routeResponse.Content.ReadAsStringAsync(); + throw new Exception($"Route creation failed: {routeResponse.StatusCode} - {errorContent}"); + } + + var route = await routeResponse.Content.ReadFromJsonAsync(JsonOptions); + + if (route == null) + { + throw new Exception("No route data returned"); + } + + Console.WriteLine($"✓ Route created with {route.TotalPoints} total points"); + Console.WriteLine($" Distance: {route.TotalDistanceMeters:F2}m"); + Console.WriteLine($" Request Maps: {route.RequestMaps}"); + Console.WriteLine($" Maps Ready: {route.MapsReady}"); + Console.WriteLine(); + + var startPoints = route.Points.Count(p => p.PointType == "start"); + var endPoints = route.Points.Count(p => p.PointType == "end"); + var actionPoints = route.Points.Count(p => p.PointType == "action"); + var intermediatePoints = route.Points.Count(p => p.PointType == "intermediate"); + + Console.WriteLine("Point Type Distribution:"); + Console.WriteLine($" Start: {startPoints}"); + Console.WriteLine($" Action: {actionPoints}"); + Console.WriteLine($" Intermediate: {intermediatePoints}"); + Console.WriteLine($" End: {endPoints}"); + Console.WriteLine(); + + if (startPoints != 1) + { + throw new Exception($"Expected 1 start point, got {startPoints}"); + } + + if (endPoints != 1) + { + throw new Exception($"Expected 1 end point, got {endPoints}"); + } + + if (actionPoints != 8) + { + throw new Exception($"Expected 8 action points (excluding start/end), got {actionPoints}"); + } + + if (!route.RequestMaps) + { + throw new Exception("Expected RequestMaps to be true"); + } + + if (route.MapsReady) + { + throw new Exception("Expected MapsReady to be false initially"); + } + + Console.WriteLine("Step 2: Waiting for complex route maps to be ready"); + Console.WriteLine(" (Processing route point regions + 2 geofence regions SEQUENTIALLY to avoid API throttling)"); + Console.WriteLine(" (This will take several minutes as each region is processed one at a time)"); + Console.WriteLine(); + + RouteResponseModel? finalRoute = null; + int maxAttempts = 240; + int pollInterval = 3000; + + for (int attempt = 0; attempt < maxAttempts; attempt++) + { + await Task.Delay(pollInterval); + + var getResponse = await httpClient.GetAsync($"/api/satellite/route/{routeId}"); + + if (!getResponse.IsSuccessStatusCode) + { + throw new Exception($"Failed to get route status: {getResponse.StatusCode}"); + } + + var currentRoute = await getResponse.Content.ReadFromJsonAsync(JsonOptions); + + if (currentRoute == null) + { + throw new Exception("No route returned"); + } + + if (currentRoute.MapsReady) + { + finalRoute = currentRoute; + Console.WriteLine($"✓ Complex route maps ready in approximately {attempt * pollInterval / 1000}s"); + break; + } + + if (attempt % 10 == 0) + { + Console.WriteLine($" Waiting... (attempt {attempt + 1}/{maxAttempts})"); + } + + if (attempt == maxAttempts - 1) + { + throw new Exception($"Timeout: Complex route maps did not become ready in {maxAttempts * pollInterval / 1000}s"); + } + } + + if (finalRoute == null) + { + throw new Exception("Final route is null"); + } + + Console.WriteLine(); + + Console.WriteLine("Step 3: Verifying generated files"); + + if (string.IsNullOrEmpty(finalRoute.CsvFilePath)) + { + throw new Exception("CSV file path is null or empty"); + } + + if (string.IsNullOrEmpty(finalRoute.SummaryFilePath)) + { + throw new Exception("Summary file path is null or empty"); + } + + if (string.IsNullOrEmpty(finalRoute.StitchedImagePath)) + { + throw new Exception("Stitched image path is null or empty"); + } + + if (!File.Exists(finalRoute.CsvFilePath)) + { + throw new Exception($"CSV file not found: {finalRoute.CsvFilePath}"); + } + + if (!File.Exists(finalRoute.SummaryFilePath)) + { + throw new Exception($"Summary file not found: {finalRoute.SummaryFilePath}"); + } + + if (!File.Exists(finalRoute.StitchedImagePath)) + { + throw new Exception($"Stitched image not found: {finalRoute.StitchedImagePath}"); + } + + var csvLines = await File.ReadAllLinesAsync(finalRoute.CsvFilePath); + var uniqueTileCount = csvLines.Length - 1; + + var stitchedInfo = new FileInfo(finalRoute.StitchedImagePath); + + Console.WriteLine("Files Generated:"); + Console.WriteLine($" ✓ CSV: {Path.GetFileName(finalRoute.CsvFilePath)} ({uniqueTileCount} tiles)"); + Console.WriteLine($" ✓ Summary: {Path.GetFileName(finalRoute.SummaryFilePath)}"); + Console.WriteLine($" ✓ Stitched Map: {Path.GetFileName(finalRoute.StitchedImagePath)} ({stitchedInfo.Length / 1024:F2} KB)"); + Console.WriteLine(); + + Console.WriteLine("Route Summary:"); + Console.WriteLine($" Route ID: {finalRoute.Id}"); + Console.WriteLine($" Total Points: {finalRoute.TotalPoints}"); + Console.WriteLine($" Action Points: {actionPoints}"); + Console.WriteLine($" Distance: {finalRoute.TotalDistanceMeters:F2}m"); + Console.WriteLine($" Geofence Regions: 2"); + Console.WriteLine($" Unique Tiles: {uniqueTileCount}"); + Console.WriteLine($" Maps Ready: {finalRoute.MapsReady}"); + Console.WriteLine(); + + if (uniqueTileCount < 10) + { + throw new Exception($"Expected at least 10 unique tiles for complex route with geofences, got {uniqueTileCount}"); + } + + if (stitchedInfo.Length < 1024) + { + throw new Exception($"Stitched image seems too small: {stitchedInfo.Length} bytes"); + } + + Console.WriteLine("✓ Complex Route with 10 Points + 2 Geofences Test: PASSED"); + } + + public static async Task RunComplexRouteWithStitchingAndGeofences(HttpClient httpClient) + { + Console.WriteLine(); + Console.WriteLine("Test: Complex Route with 10 Points + 2 Geofences - Region Processing and Stitching"); + Console.WriteLine("======================================================================================"); + Console.WriteLine(); + var routeId = Guid.NewGuid(); var request = new CreateRouteRequest { @@ -347,7 +569,7 @@ public static class RouteTests for (int i = 0; i < request.Geofences.Polygons.Count; i++) { var polygon = request.Geofences.Polygons[i]; - Console.WriteLine($" {i + 1}. NW: ({polygon.NorthWest.Lat}, {polygon.NorthWest.Lon}) -> SE: ({polygon.SouthEast.Lat}, {polygon.SouthEast.Lon})"); + Console.WriteLine($" {i + 1}. NW: ({polygon.NorthWest?.Lat}, {polygon.NorthWest?.Lon}) -> SE: ({polygon.SouthEast?.Lat}, {polygon.SouthEast?.Lon})"); } Console.WriteLine(); Console.WriteLine("Route Path:"); diff --git a/SatelliteProvider.Services/RouteProcessingService.cs b/SatelliteProvider.Services/RouteProcessingService.cs index 6e24f2a..491bd8b 100644 --- a/SatelliteProvider.Services/RouteProcessingService.cs +++ b/SatelliteProvider.Services/RouteProcessingService.cs @@ -326,54 +326,49 @@ public class RouteProcessingService : BackgroundService string? stitchedImagePath = null; if (route.RequestMaps) { - var geofenceTileBounds = new List<(Guid RegionId, int MinX, int MinY, int MaxX, int MaxY)>(); + int? minX = null, minY = null, maxX = null, maxY = null; - foreach (var geofenceId in geofenceRegionIds) + if (geofenceRegionIds.Count > 0) { - var region = await _regionRepository.GetByIdAsync(geofenceId); - if (region != null && !string.IsNullOrEmpty(region.CsvFilePath) && File.Exists(region.CsvFilePath)) + foreach (var geofenceId in geofenceRegionIds) { - _logger.LogInformation("Route {RouteId}: Loading geofence region {RegionId} tile bounds", - routeId, region.Id); - - var csvLines = await File.ReadAllLinesAsync(region.CsvFilePath, cancellationToken); - int? minX = null, minY = null, maxX = null, maxY = null; - - foreach (var line in csvLines.Skip(1)) + var region = await _regionRepository.GetByIdAsync(geofenceId); + if (region != null && !string.IsNullOrEmpty(region.CsvFilePath) && File.Exists(region.CsvFilePath)) { - var parts = line.Split(','); - if (parts.Length >= 3) + var csvLines = await File.ReadAllLinesAsync(region.CsvFilePath, cancellationToken); + + foreach (var line in csvLines.Skip(1)) { - if (double.TryParse(parts[0], out var lat) && double.TryParse(parts[1], out var lon)) + var parts = line.Split(','); + if (parts.Length >= 3) { - var tile = GeoUtils.WorldToTilePos(new Common.DTO.GeoPoint { Lat = lat, Lon = lon }, route.ZoomLevel); - minX = minX == null ? tile.x : Math.Min(minX.Value, tile.x); - minY = minY == null ? tile.y : Math.Min(minY.Value, tile.y); - maxX = maxX == null ? tile.x : Math.Max(maxX.Value, tile.x); - maxY = maxY == null ? tile.y : Math.Max(maxY.Value, tile.y); + if (double.TryParse(parts[0], out var lat) && double.TryParse(parts[1], out var lon)) + { + var tile = GeoUtils.WorldToTilePos(new Common.DTO.GeoPoint { Lat = lat, Lon = lon }, route.ZoomLevel); + minX = minX == null ? tile.x : Math.Min(minX.Value, tile.x); + minY = minY == null ? tile.y : Math.Min(minY.Value, tile.y); + maxX = maxX == null ? tile.x : Math.Max(maxX.Value, tile.x); + maxY = maxY == null ? tile.y : Math.Max(maxY.Value, tile.y); + } } } } - - if (minX.HasValue && minY.HasValue && maxX.HasValue && maxY.HasValue) - { - geofenceTileBounds.Add((region.Id, minX.Value, minY.Value, maxX.Value, maxY.Value)); - _logger.LogInformation("Route {RouteId}: Geofence {RegionId} tile bounds: X=[{MinX}..{MaxX}], Y=[{MinY}..{MaxY}]", - routeId, region.Id, minX.Value, maxX.Value, minY.Value, maxY.Value); - } } - else + + if (minX.HasValue && minY.HasValue && maxX.HasValue && maxY.HasValue) { - _logger.LogWarning("Route {RouteId}: Geofence region {RegionId} CSV not found", - routeId, geofenceId); + _logger.LogInformation("Route {RouteId}: Combined geofence tile bounds: X=[{MinX}..{MaxX}], Y=[{MinY}..{MaxY}]", + routeId, minX.Value, maxX.Value, minY.Value, maxY.Value); } } - _logger.LogInformation("Route {RouteId}: Starting stitching with {GeofenceCount} geofence regions", - routeId, geofenceTileBounds.Count); - stitchedImagePath = Path.Combine(readyDir, $"route_{routeId}_stitched.jpg"); - await StitchRouteTilesAsync(allTiles.Values.ToList(), stitchedImagePath, route.ZoomLevel, geofenceTileBounds, cancellationToken); + + 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); } var summaryPath = Path.Combine(readyDir, $"route_{routeId}_summary.txt"); @@ -460,7 +455,7 @@ public class RouteProcessingService : BackgroundService List tiles, string outputPath, int zoomLevel, - List<(Guid RegionId, int MinX, int MinY, int MaxX, int MaxY)> geofenceTileBounds, + (int MinX, int MinY, int MaxX, int MaxY)? geofenceBounds, CancellationToken cancellationToken) { if (tiles.Count == 0) @@ -559,46 +554,42 @@ public class RouteProcessingService : BackgroundService } } - if (geofenceTileBounds.Count > 0) + if (geofenceBounds.HasValue) { - _logger.LogInformation("Drawing {Count} geofence borders on image {Width}x{Height} (grid: minX={MinX}, minY={MinY})", - geofenceTileBounds.Count, imageWidth, imageHeight, minX, minY); + var (geoMinX, geoMinY, geoMaxX, geoMaxY) = geofenceBounds.Value; - foreach (var (regionId, geoMinX, geoMinY, geoMaxX, geoMaxY) in geofenceTileBounds) + _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) { - _logger.LogInformation("Geofence {RegionId}: Tile range - X=[{MinX}..{MaxX}], Y=[{MinY}..{MaxY}]", - regionId, geoMinX, geoMaxX, geoMinY, geoMaxY); + _logger.LogInformation("Drawing geofence border at pixel coords ({X1},{Y1}) to ({X2},{Y2})", + x1, y1, x2, y2); - var x1 = (geoMinX - minX) * tileSizePixels; - var y1 = (geoMinY - minY) * tileSizePixels; - var x2 = (geoMaxX - minX + 1) * tileSizePixels - 1; - var y2 = (geoMaxY - minY + 1) * tileSizePixels - 1; + DrawRectangleBorder(stitchedImage, x1, y1, x2, y2, new Rgb24(255, 255, 0)); - _logger.LogInformation("Geofence {RegionId}: Pixel coords before clipping - ({X1},{Y1}) to ({X2},{Y2})", - regionId, 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("Geofence {RegionId}: Drawing border at pixel coords ({X1},{Y1}) to ({X2},{Y2})", - regionId, x1, y1, x2, y2); - - DrawRectangleBorder(stitchedImage, x1, y1, x2, y2, new Rgb24(255, 255, 0)); - - _logger.LogInformation("Successfully drew geofence border for region {RegionId}", regionId); - } - else - { - _logger.LogWarning("Geofence {RegionId}: Border out of bounds or invalid - ({X1},{Y1}) to ({X2},{Y2}), image size: {Width}x{Height}", - regionId, x1, y1, x2, y2, imageWidth, imageHeight); - } + _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); } - - _logger.LogInformation("Completed drawing all geofence borders, now saving image..."); } await stitchedImage.SaveAsJpegAsync(outputPath, cancellationToken); @@ -684,21 +675,6 @@ public class RouteProcessingService : BackgroundService return (-1, -1); } - private static (double NorthLat, double SouthLat, double WestLon, double EastLon) CalculateGeofenceCorners( - double centerLat, - double centerLon, - double halfSizeMeters) - { - var center = new Common.DTO.GeoPoint { Lat = centerLat, Lon = centerLon }; - - var north = GeoUtils.GoDirection(center, new Common.DTO.Direction { Distance = halfSizeMeters, Azimuth = 0 }); - var south = GeoUtils.GoDirection(center, new Common.DTO.Direction { Distance = halfSizeMeters, Azimuth = 180 }); - var east = GeoUtils.GoDirection(center, new Common.DTO.Direction { Distance = halfSizeMeters, Azimuth = 90 }); - var west = GeoUtils.GoDirection(center, new Common.DTO.Direction { Distance = halfSizeMeters, Azimuth = 270 }); - - return (north.Lat, south.Lat, west.Lon, east.Lon); - } - private static void DrawRectangleBorder(Image image, int x1, int y1, int x2, int y2, Rgb24 color) { const int thickness = 5; diff --git a/SatelliteProvider.Services/RouteService.cs b/SatelliteProvider.Services/RouteService.cs index 3dd4cc2..95efe5f 100644 --- a/SatelliteProvider.Services/RouteService.cs +++ b/SatelliteProvider.Services/RouteService.cs @@ -192,24 +192,28 @@ public class RouteService : IRouteService throw new ArgumentException("Geofence northWest latitude must be greater than southEast latitude"); } - var center = GeoUtils.CalculateCenter(polygon.NorthWest, polygon.SouthEast); - var diagonalDistance = GeoUtils.CalculatePolygonDiagonalDistance(polygon.NorthWest, polygon.SouthEast); - var geofenceRegionSize = Math.Max(diagonalDistance * 0.6, request.RegionSizeMeters); - - var geofenceRegionId = Guid.NewGuid(); + var geofenceRegions = CreateGeofenceRegionGrid(polygon.NorthWest, polygon.SouthEast, request.RegionSizeMeters); - _logger.LogInformation("Route {RouteId}: Requesting geofence region {RegionId} at center ({Lat}, {Lon}) with size {Size}m", - request.Id, geofenceRegionId, center.Lat, center.Lon, geofenceRegionSize); + _logger.LogInformation("Route {RouteId}: Created grid of {Count} regions to cover geofence area", + request.Id, geofenceRegions.Count); - await _regionService.RequestRegionAsync( - geofenceRegionId, - center.Lat, - center.Lon, - geofenceRegionSize, - request.ZoomLevel, - stitchTiles: false); - - await _routeRepository.LinkRouteToRegionAsync(request.Id, geofenceRegionId, isGeofence: true); + 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); + + await _regionService.RequestRegionAsync( + geofenceRegionId, + geofencePoint.Lat, + geofencePoint.Lon, + request.RegionSizeMeters, + request.ZoomLevel, + stitchTiles: false); + + await _routeRepository.LinkRouteToRegionAsync(request.Id, geofenceRegionId, isGeofence: true); + } } } @@ -278,5 +282,36 @@ public class RouteService : IRouteService UpdatedAt = route.UpdatedAt }; } + + private List CreateGeofenceRegionGrid(GeoPoint northWest, GeoPoint southEast, double regionSizeMeters) + { + var regions = new List(); + + var northPoint = new GeoPoint(northWest.Lat, (northWest.Lon + southEast.Lon) / 2); + var southPoint = new GeoPoint(southEast.Lat, (northWest.Lon + southEast.Lon) / 2); + var heightMeters = GeoUtils.CalculateDistance(northPoint, southPoint); + + var westPoint = new GeoPoint((northWest.Lat + southEast.Lat) / 2, northWest.Lon); + var eastPoint = new GeoPoint((northWest.Lat + southEast.Lat) / 2, southEast.Lon); + var widthMeters = GeoUtils.CalculateDistance(westPoint, eastPoint); + + var numLatSteps = Math.Max(1, (int)Math.Ceiling(heightMeters / regionSizeMeters)); + var numLonSteps = Math.Max(1, (int)Math.Ceiling(widthMeters / regionSizeMeters)); + + var latStep = (northWest.Lat - southEast.Lat) / numLatSteps; + var lonStep = (southEast.Lon - northWest.Lon) / numLonSteps; + + for (int latIdx = 0; latIdx < numLatSteps; latIdx++) + { + for (int lonIdx = 0; lonIdx < numLonSteps; lonIdx++) + { + var lat = northWest.Lat - (latIdx + 0.5) * latStep; + var lon = northWest.Lon + (lonIdx + 0.5) * lonStep; + regions.Add(new GeoPoint(lat, lon)); + } + } + + return regions; + } } diff --git a/SatelliteProvider.Tests/GoogleMapsDownloaderTests.cs b/SatelliteProvider.Tests/GoogleMapsDownloaderTests.cs index 3b7bd20..b46fd00 100644 --- a/SatelliteProvider.Tests/GoogleMapsDownloaderTests.cs +++ b/SatelliteProvider.Tests/GoogleMapsDownloaderTests.cs @@ -13,7 +13,7 @@ namespace SatelliteProvider.Tests; public class DummyTests { [Fact] - public async Task Dummy_ShouldWork() + public void Dummy_ShouldWork() { Assert.Equal(1, 1); }