mirror of
https://github.com/azaion/satellite-provider.git
synced 2026-06-23 11:41:14 +00:00
275ee1b554
- Introduced new TileProvision settings in appsettings.json, including MaxTilesPerBatch and ProgressEmitIntervalSeconds. - Configured TileProvisionConfig in Program.cs to bind the new settings. - Added gRPC service for RouteTileDelivery in Program.cs to handle tile delivery requests. - Updated SatelliteProvider.Api.csproj to include Grpc.AspNetCore package and added protobuf file for tile provision. - Enhanced AuthenticationServiceCollectionExtensions to handle JWT token extraction from the Authorization header. - Registered additional services in RouteManagementServiceCollectionExtensions for tile processing. These changes enhance the API's capability to manage tile provisioning and delivery efficiently.
206 lines
7.3 KiB
C#
206 lines
7.3 KiB
C#
using Google.Protobuf.WellKnownTypes;
|
|
using Grpc.Core;
|
|
using Microsoft.AspNetCore.Authorization;
|
|
using Satellite.V1;
|
|
using SatelliteProvider.Services.RouteManagement.TileProvision;
|
|
|
|
namespace SatelliteProvider.Api.Grpc;
|
|
|
|
[Authorize]
|
|
public sealed class RouteTileDeliveryGrpcService : RouteTileDelivery.RouteTileDeliveryBase
|
|
{
|
|
private readonly RouteTileDeliveryOrchestrator _orchestrator;
|
|
private readonly ILogger<RouteTileDeliveryGrpcService> _logger;
|
|
|
|
public RouteTileDeliveryGrpcService(
|
|
RouteTileDeliveryOrchestrator orchestrator,
|
|
ILogger<RouteTileDeliveryGrpcService> logger)
|
|
{
|
|
_orchestrator = orchestrator;
|
|
_logger = logger;
|
|
}
|
|
|
|
public override async Task DeliverRouteTiles(
|
|
DeliverRouteTilesRequest request,
|
|
IServerStreamWriter<RouteTileEvent> responseStream,
|
|
ServerCallContext context)
|
|
{
|
|
if (request.Route is null)
|
|
{
|
|
await WriteErrorAsync(responseStream, "INVALID_REQUEST", "route is required", retryable: false, context.CancellationToken);
|
|
return;
|
|
}
|
|
|
|
if (!Guid.TryParse(request.Route.RouteId, out var routeId))
|
|
{
|
|
await WriteErrorAsync(responseStream, "INVALID_REQUEST", "route_id must be a UUID", retryable: false, context.CancellationToken);
|
|
return;
|
|
}
|
|
|
|
var job = MapJob(request, routeId);
|
|
var sink = new GrpcRouteTileDeliverySink(responseStream, context.CancellationToken);
|
|
|
|
try
|
|
{
|
|
await _orchestrator.DeliverAsync(job, sink, context.CancellationToken);
|
|
}
|
|
catch (ArgumentException ex)
|
|
{
|
|
_logger.LogWarning(ex, "Invalid route tile delivery request for route {RouteId}", routeId);
|
|
await WriteErrorAsync(responseStream, "INVALID_REQUEST", ex.Message, retryable: false, context.CancellationToken);
|
|
}
|
|
catch (OperationCanceledException) when (context.CancellationToken.IsCancellationRequested)
|
|
{
|
|
throw;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Route tile delivery failed for route {RouteId}", routeId);
|
|
await WriteErrorAsync(responseStream, "INTERNAL_ERROR", ex.Message, retryable: true, context.CancellationToken);
|
|
}
|
|
}
|
|
|
|
private static RouteTileDeliveryJob MapJob(DeliverRouteTilesRequest request, Guid routeId)
|
|
{
|
|
var route = request.Route!;
|
|
var waypoints = route.Waypoints
|
|
.Select(w => (w.Lat, w.Lon))
|
|
.ToList();
|
|
|
|
var geofences = route.Geofences
|
|
.Select(polygon => (IReadOnlyList<(double Lat, double Lon)>)polygon.Vertices
|
|
.Select(v => (v.Lat, v.Lon))
|
|
.ToList())
|
|
.ToList();
|
|
|
|
var clientTiles = request.ClientTiles
|
|
.Select(MapClientTile)
|
|
.ToList();
|
|
|
|
return new RouteTileDeliveryJob(
|
|
routeId,
|
|
waypoints,
|
|
route.RegionSizeMeters,
|
|
route.Zoom,
|
|
geofences,
|
|
route.IncludeGeofenceTiles,
|
|
clientTiles);
|
|
}
|
|
|
|
private static ClientTileSnapshot MapClientTile(ClientTileRecord record)
|
|
{
|
|
var capturedAt = record.CapturedAt?.ToDateTime().ToUniversalTime() ?? DateTime.MinValue;
|
|
byte[]? hash = record.ContentSha256 is { Length: > 0 } sha ? sha.ToByteArray() : null;
|
|
return new ClientTileSnapshot(
|
|
record.Z,
|
|
record.X,
|
|
record.Y,
|
|
record.ResolutionMPerPx,
|
|
capturedAt,
|
|
hash);
|
|
}
|
|
|
|
private static async Task WriteErrorAsync(
|
|
IServerStreamWriter<RouteTileEvent> responseStream,
|
|
string code,
|
|
string message,
|
|
bool retryable,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
await responseStream.WriteAsync(new RouteTileEvent
|
|
{
|
|
Error = new DeliveryError
|
|
{
|
|
Code = code,
|
|
Message = message,
|
|
Retryable = retryable,
|
|
},
|
|
}, cancellationToken);
|
|
}
|
|
|
|
private sealed class GrpcRouteTileDeliverySink : IRouteTileDeliverySink
|
|
{
|
|
private readonly IServerStreamWriter<RouteTileEvent> _stream;
|
|
|
|
public GrpcRouteTileDeliverySink(IServerStreamWriter<RouteTileEvent> stream, CancellationToken cancellationToken)
|
|
{
|
|
_stream = stream;
|
|
_ = cancellationToken;
|
|
}
|
|
|
|
public async ValueTask WriteManifestAsync(uint totalCandidates, uint skippedByClient, uint toDeliver, CancellationToken cancellationToken)
|
|
{
|
|
await _stream.WriteAsync(new RouteTileEvent
|
|
{
|
|
Manifest = new RouteManifest
|
|
{
|
|
TotalCandidates = totalCandidates,
|
|
SkippedByClient = skippedByClient,
|
|
ToDeliver = toDeliver,
|
|
},
|
|
}, cancellationToken);
|
|
}
|
|
|
|
public async ValueTask WriteBatchAsync(uint batchSeq, IReadOnlyList<PreparedTileDelivery> tiles, CancellationToken cancellationToken)
|
|
{
|
|
var batch = new TileBatch { BatchSeq = batchSeq };
|
|
foreach (var tile in tiles)
|
|
{
|
|
batch.Tiles.Add(new TilePayload
|
|
{
|
|
Z = tile.Candidate.Z,
|
|
X = tile.Candidate.X,
|
|
Y = tile.Candidate.Y,
|
|
ResolutionMPerPx = tile.ResolutionMetersPerPx,
|
|
CapturedAt = Timestamp.FromDateTime(DateTime.SpecifyKind(tile.CapturedAtUtc, DateTimeKind.Utc)),
|
|
Source = tile.Source,
|
|
Jpeg = Google.Protobuf.ByteString.CopyFrom(tile.Jpeg),
|
|
ContentSha256 = Google.Protobuf.ByteString.CopyFrom(tile.ContentSha256),
|
|
RoutePriority = tile.Candidate.RoutePriority,
|
|
});
|
|
}
|
|
|
|
await _stream.WriteAsync(new RouteTileEvent { Batch = batch }, cancellationToken);
|
|
}
|
|
|
|
public async ValueTask WriteProgressAsync(uint delivered, uint total, uint downloading, CancellationToken cancellationToken)
|
|
{
|
|
await _stream.WriteAsync(new RouteTileEvent
|
|
{
|
|
Progress = new ProgressUpdate
|
|
{
|
|
Delivered = delivered,
|
|
Total = total,
|
|
Downloading = downloading,
|
|
},
|
|
}, cancellationToken);
|
|
}
|
|
|
|
public async ValueTask WriteCompleteAsync(uint delivered, uint skippedClient, uint skippedServerFilter, CancellationToken cancellationToken)
|
|
{
|
|
await _stream.WriteAsync(new RouteTileEvent
|
|
{
|
|
Complete = new DeliveryComplete
|
|
{
|
|
Delivered = delivered,
|
|
SkippedClient = skippedClient,
|
|
SkippedServerFilter = skippedServerFilter,
|
|
},
|
|
}, cancellationToken);
|
|
}
|
|
|
|
public async ValueTask WriteErrorAsync(string code, string message, bool retryable, CancellationToken cancellationToken)
|
|
{
|
|
await _stream.WriteAsync(new RouteTileEvent
|
|
{
|
|
Error = new DeliveryError
|
|
{
|
|
Code = code,
|
|
Message = message,
|
|
Retryable = retryable,
|
|
},
|
|
}, cancellationToken);
|
|
}
|
|
}
|
|
}
|