mirror of
https://github.com/azaion/satellite-provider.git
synced 2026-06-21 08:11:13 +00:00
1dcd089d39
Promotes 8 operational levers into config keys with defaults that match the prior source literals byte-for-byte: ProcessingConfig: RegionProcessingTimeoutSeconds (300), RouteProcessingPollIntervalSeconds (5), MaxRoutePointSpacingMeters (200), LatLonTolerance (0.0001). MapConfig: TileSizePixels (256), AllowedZoomLevels ([15..19]), RetryBaseDelaySeconds (1), RetryMaxDelaySeconds (30). Sites updated: RegionService, RouteProcessingService, RoutePointGraphBuilder, RouteValidator, RouteService 4-arg ctor, RouteImageRenderer, GoogleMapsDownloaderV2, TileService. Closes LF-2 by forwarding HttpContext.RequestAborted from GetTileByLatLon into the downloader. appsettings.json gains the 8 new keys at default values. Tests: 141 / 141 unit + 5 / 5 smoke green. New ConfigDefaultsTests pins defaults to original literals; new TileService unit test asserts CT identity from caller to downloader (AZ-371 AC-3). Co-authored-by: Cursor <cursoragent@cursor.com>
161 lines
5.5 KiB
C#
161 lines
5.5 KiB
C#
using Microsoft.Extensions.Logging;
|
|
using Microsoft.Extensions.Options;
|
|
using SatelliteProvider.Common.Configs;
|
|
using SatelliteProvider.Common.DTO;
|
|
using SatelliteProvider.Common.Interfaces;
|
|
using SatelliteProvider.DataAccess.Models;
|
|
using SatelliteProvider.DataAccess.Repositories;
|
|
|
|
namespace SatelliteProvider.Services.RouteManagement;
|
|
|
|
public class RouteService : IRouteService
|
|
{
|
|
private readonly IRouteRepository _routeRepository;
|
|
private readonly IRegionService _regionService;
|
|
private readonly ILogger<RouteService> _logger;
|
|
private readonly RouteValidator _validator;
|
|
private readonly RoutePointGraphBuilder _pointGraphBuilder;
|
|
private readonly GeofenceGridCalculator _geofenceGridCalculator;
|
|
private readonly RouteResponseMapper _responseMapper;
|
|
|
|
public RouteService(
|
|
IRouteRepository routeRepository,
|
|
IRegionService regionService,
|
|
IOptions<ProcessingConfig> processingConfig,
|
|
ILogger<RouteService> logger)
|
|
: this(routeRepository, regionService, logger,
|
|
new RouteValidator(processingConfig),
|
|
new RoutePointGraphBuilder(processingConfig),
|
|
new GeofenceGridCalculator(),
|
|
new RouteResponseMapper())
|
|
{
|
|
}
|
|
|
|
public RouteService(
|
|
IRouteRepository routeRepository,
|
|
IRegionService regionService,
|
|
ILogger<RouteService> logger,
|
|
RouteValidator validator,
|
|
RoutePointGraphBuilder pointGraphBuilder,
|
|
GeofenceGridCalculator geofenceGridCalculator,
|
|
RouteResponseMapper responseMapper)
|
|
{
|
|
_routeRepository = routeRepository;
|
|
_regionService = regionService;
|
|
_logger = logger;
|
|
_validator = validator;
|
|
_pointGraphBuilder = pointGraphBuilder;
|
|
_geofenceGridCalculator = geofenceGridCalculator;
|
|
_responseMapper = responseMapper;
|
|
}
|
|
|
|
public async Task<RouteResponse> CreateRouteAsync(CreateRouteRequest request)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(request);
|
|
|
|
// AZ-362: idempotent POST contract. A retried POST with the same caller-supplied
|
|
// Id returns the existing route instead of re-running point generation and
|
|
// re-queueing geofence regions.
|
|
var existing = await GetRouteAsync(request.Id);
|
|
if (existing != null)
|
|
{
|
|
_logger.LogInformation(
|
|
"Idempotent route POST: id {RouteId} already exists; returning existing resource",
|
|
request.Id);
|
|
return existing;
|
|
}
|
|
|
|
_validator.Validate(request);
|
|
|
|
var graph = _pointGraphBuilder.Build(request.Points);
|
|
|
|
var now = DateTime.UtcNow;
|
|
var routeEntity = new RouteEntity
|
|
{
|
|
Id = request.Id,
|
|
Name = request.Name,
|
|
Description = request.Description,
|
|
RegionSizeMeters = request.RegionSizeMeters,
|
|
ZoomLevel = request.ZoomLevel,
|
|
TotalDistanceMeters = graph.TotalDistanceMeters,
|
|
TotalPoints = graph.Points.Count,
|
|
RequestMaps = request.RequestMaps,
|
|
CreateTilesZip = request.CreateTilesZip,
|
|
MapsReady = false,
|
|
CreatedAt = now,
|
|
UpdatedAt = now,
|
|
};
|
|
|
|
await _routeRepository.InsertRouteAsync(routeEntity);
|
|
|
|
var pointEntities = graph.Points.Select(p => new RoutePointEntity
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
RouteId = request.Id,
|
|
SequenceNumber = p.SequenceNumber,
|
|
Latitude = p.Latitude,
|
|
Longitude = p.Longitude,
|
|
PointType = p.PointType,
|
|
SegmentIndex = p.SegmentIndex,
|
|
DistanceFromPrevious = p.DistanceFromPrevious,
|
|
CreatedAt = now,
|
|
}).ToList();
|
|
|
|
await _routeRepository.InsertRoutePointsAsync(pointEntities);
|
|
|
|
await ProcessGeofencePolygonsAsync(request);
|
|
|
|
return _responseMapper.Map(routeEntity, graph.Points);
|
|
}
|
|
|
|
public async Task<RouteResponse?> GetRouteAsync(Guid id)
|
|
{
|
|
var route = await _routeRepository.GetByIdAsync(id);
|
|
if (route == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var points = await _routeRepository.GetRoutePointsAsync(id);
|
|
return _responseMapper.Map(route, points);
|
|
}
|
|
|
|
private async Task ProcessGeofencePolygonsAsync(CreateRouteRequest request)
|
|
{
|
|
var polygons = request.Geofences?.Polygons;
|
|
if (polygons is null || polygons.Count == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (int polygonIndex = 0; polygonIndex < polygons.Count; polygonIndex++)
|
|
{
|
|
var polygon = polygons[polygonIndex];
|
|
// Validator (above) guarantees NorthWest/SouthEast are non-null and well-formed.
|
|
var regions = _geofenceGridCalculator.GenerateRegions(
|
|
polygon.NorthWest!,
|
|
polygon.SouthEast!,
|
|
request.RegionSizeMeters);
|
|
|
|
foreach (var center in regions)
|
|
{
|
|
var geofenceRegionId = Guid.NewGuid();
|
|
|
|
await _regionService.RequestRegionAsync(
|
|
geofenceRegionId,
|
|
center.Lat,
|
|
center.Lon,
|
|
request.RegionSizeMeters,
|
|
request.ZoomLevel,
|
|
stitchTiles: false);
|
|
|
|
await _routeRepository.LinkRouteToRegionAsync(
|
|
request.Id,
|
|
geofenceRegionId,
|
|
isGeofence: true,
|
|
geofencePolygonIndex: polygonIndex);
|
|
}
|
|
}
|
|
}
|
|
}
|