mirror of
https://github.com/azaion/annotations.git
synced 2026-04-23 01:46:31 +00:00
update AI initializing
rework AIAvailabilityStatus events to mediatr
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
using MediatR;
|
||||
|
||||
namespace Azaion.Common.Services;
|
||||
|
||||
public class GPSMatcherEventHandler(IGpsMatcherService gpsMatcherService) :
|
||||
INotificationHandler<GPSMatcherResultEvent>,
|
||||
INotificationHandler<GPSMatcherFinishedEvent>
|
||||
{
|
||||
public async Task Handle(GPSMatcherResultEvent result, CancellationToken cancellationToken) =>
|
||||
await gpsMatcherService.SetGpsResult(result, cancellationToken);
|
||||
|
||||
public async Task Handle(GPSMatcherFinishedEvent notification, CancellationToken cancellationToken) =>
|
||||
await gpsMatcherService.FinishGPS(notification, cancellationToken);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using Azaion.Common.DTO;
|
||||
using MediatR;
|
||||
|
||||
namespace Azaion.Common.Services;
|
||||
|
||||
public enum MatchTypeEnum
|
||||
{
|
||||
None = -1,
|
||||
MatchTypeSingle = 0,
|
||||
MatchTypeStitched = 1,
|
||||
MatchTypeOpticalFlow = 2,
|
||||
MatchTypeInterpolated = 3,
|
||||
MatchTypeFailure = 4
|
||||
}
|
||||
|
||||
public class GPSMatcherResultEvent : INotification
|
||||
{
|
||||
public int Index { get; set; }
|
||||
public string Image { get; set; } = null!;
|
||||
public GeoPoint GeoPoint { get; set; } = null!;
|
||||
public int KeyPoints { get; set; }
|
||||
public MatchTypeEnum MatchType { get; set; }
|
||||
}
|
||||
|
||||
public class GPSMatcherResultProcessedEvent : GPSMatcherResultEvent
|
||||
{
|
||||
public GeoPoint ProcessedGeoPoint { get; set; } = null!;
|
||||
|
||||
public GPSMatcherResultProcessedEvent() { }
|
||||
|
||||
public GPSMatcherResultProcessedEvent(GPSMatcherResultEvent gpsMatcherResultEvent, GeoPoint processedGeoPoint)
|
||||
{
|
||||
Index = gpsMatcherResultEvent.Index;
|
||||
Image = gpsMatcherResultEvent.Image;
|
||||
GeoPoint = gpsMatcherResultEvent.GeoPoint;
|
||||
KeyPoints = gpsMatcherResultEvent.KeyPoints;
|
||||
MatchType = gpsMatcherResultEvent.MatchType;
|
||||
ProcessedGeoPoint = processedGeoPoint;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public class GPSMatcherJobAcceptedEvent : INotification {}
|
||||
|
||||
public class GPSMatcherFinishedEvent : INotification {}
|
||||
@@ -0,0 +1,113 @@
|
||||
using System.IO;
|
||||
using Azaion.Common.DTO;
|
||||
using Azaion.Common.DTO.Config;
|
||||
using Azaion.Common.Extensions;
|
||||
using MediatR;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Azaion.Common.Services;
|
||||
|
||||
public interface IGpsMatcherService
|
||||
{
|
||||
Task RunGpsMatching(string userRouteDir, GeoPoint geoPoint, CancellationToken detectToken = default);
|
||||
void StopGpsMatching();
|
||||
Task SetGpsResult(GPSMatcherResultEvent result, CancellationToken detectToken = default);
|
||||
Task FinishGPS(GPSMatcherFinishedEvent notification, CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
public class GpsMatcherService(IGpsMatcherClient gpsMatcherClient,
|
||||
ISatelliteDownloader satelliteTileDownloader,
|
||||
IOptions<DirectoriesConfig> dirConfig,
|
||||
IOptions<GpsDeniedConfig> gpsDeniedConfig,
|
||||
IMediator mediator) : IGpsMatcherService
|
||||
{
|
||||
private readonly DirectoriesConfig _dirConfig = dirConfig.Value;
|
||||
private const int ZOOM_LEVEL = 18;
|
||||
private const int POINTS_COUNT = 10;
|
||||
private const int DISTANCE_BETWEEN_POINTS_M = 140;
|
||||
private const double SATELLITE_RADIUS_M = DISTANCE_BETWEEN_POINTS_M * (POINTS_COUNT + 1);
|
||||
private const int MAX_AVG_POINTS = 2;
|
||||
|
||||
private string _routeDir = "";
|
||||
private string _userRouteDir = "";
|
||||
private List<string> _allRouteImages = new();
|
||||
private Dictionary<string, int> _currentRouteImages = new();
|
||||
private GeoPoint _lastGeoPoint = new();
|
||||
private CancellationToken _detectToken;
|
||||
private int _currentIndex;
|
||||
private readonly Queue<Direction> _directions = new();
|
||||
|
||||
|
||||
public async Task RunGpsMatching(string userRouteDir, GeoPoint initGeoPoint, CancellationToken detectToken = default)
|
||||
{
|
||||
_routeDir = Path.Combine(Constants.EXTERNAL_GPS_DENIED_FOLDER, _dirConfig.GpsRouteDirectory);
|
||||
_userRouteDir = userRouteDir;
|
||||
|
||||
_allRouteImages = Directory.GetFiles(userRouteDir)
|
||||
.OrderBy(x => x).ToList();
|
||||
_lastGeoPoint = initGeoPoint;
|
||||
_detectToken = detectToken;
|
||||
await StartMatchingRound(0);
|
||||
}
|
||||
|
||||
private async Task StartMatchingRound(int startIndex)
|
||||
{
|
||||
//empty route dir
|
||||
if (Directory.Exists(_routeDir))
|
||||
Directory.Delete(_routeDir, true);
|
||||
Directory.CreateDirectory(_routeDir);
|
||||
|
||||
_currentRouteImages = _allRouteImages
|
||||
.Skip(startIndex)
|
||||
.Take(POINTS_COUNT)
|
||||
.Select((fullName, index) =>
|
||||
{
|
||||
var filename = Path.GetFileName(fullName);
|
||||
File.Copy(Path.Combine(_userRouteDir, filename), Path.Combine(_routeDir, filename));
|
||||
return new { Filename = Path.GetFileNameWithoutExtension(fullName), Index = startIndex + index };
|
||||
})
|
||||
.ToDictionary(x => x.Filename, x => x.Index);
|
||||
|
||||
await satelliteTileDownloader.GetTiles(_lastGeoPoint, SATELLITE_RADIUS_M, ZOOM_LEVEL, _detectToken);
|
||||
await gpsMatcherClient.StartMatching(new StartMatchingEvent
|
||||
{
|
||||
ImagesCount = POINTS_COUNT,
|
||||
GeoPoint = _lastGeoPoint,
|
||||
SatelliteImagesDir = _dirConfig.GpsSatDirectory,
|
||||
RouteDir = _dirConfig.GpsRouteDirectory
|
||||
});
|
||||
}
|
||||
|
||||
public void StopGpsMatching()
|
||||
{
|
||||
gpsMatcherClient.Stop();
|
||||
}
|
||||
|
||||
public async Task SetGpsResult(GPSMatcherResultEvent result, CancellationToken detectToken = default)
|
||||
{
|
||||
_currentIndex = _currentRouteImages[result.Image];
|
||||
_currentRouteImages.Remove(result.Image);
|
||||
|
||||
if (result.KeyPoints >= gpsDeniedConfig.Value.MinKeyPoints)
|
||||
{
|
||||
var direction = _lastGeoPoint.DirectionTo(result.GeoPoint);
|
||||
_directions.Enqueue(direction);
|
||||
if (_directions.Count > MAX_AVG_POINTS)
|
||||
_directions.Dequeue();
|
||||
_lastGeoPoint = result.GeoPoint;
|
||||
}
|
||||
else
|
||||
{
|
||||
var direction = new Direction(_directions.Average(x => x.Distance), _directions.Average(x => x.Azimuth));
|
||||
_lastGeoPoint = _lastGeoPoint.GoDirection(direction);
|
||||
}
|
||||
await mediator.Publish(new GPSMatcherResultProcessedEvent(result, _lastGeoPoint), detectToken);
|
||||
}
|
||||
|
||||
public async Task FinishGPS(GPSMatcherFinishedEvent notification, CancellationToken cancellationToken)
|
||||
{
|
||||
if (_currentRouteImages.Count == 0 && _currentIndex < _allRouteImages.Count)
|
||||
await StartMatchingRound(_currentIndex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using Azaion.Common.DTO;
|
||||
using Azaion.Common.Events;
|
||||
using MediatR;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using NetMQ;
|
||||
using NetMQ.Sockets;
|
||||
|
||||
namespace Azaion.Common.Services;
|
||||
|
||||
public interface IGpsMatcherClient : IDisposable
|
||||
{
|
||||
Task StartMatching(StartMatchingEvent startEvent);
|
||||
void Stop();
|
||||
}
|
||||
|
||||
public class StartMatchingEvent
|
||||
{
|
||||
public string RouteDir { get; set; } = null!;
|
||||
public string SatelliteImagesDir { get; set; } = null!;
|
||||
public int ImagesCount { get; set; }
|
||||
public GeoPoint GeoPoint { get; set; } = null!;
|
||||
public int Altitude { get; set; } = 400;
|
||||
public double CameraSensorWidth { get; set; } = 23.5;
|
||||
public double CameraFocalLength { get; set; } = 24;
|
||||
|
||||
public override string ToString() =>
|
||||
$"{RouteDir},{SatelliteImagesDir},{ImagesCount},{GeoPoint.Lat},{GeoPoint.Lon},{Altitude},{CameraSensorWidth},{CameraFocalLength}";
|
||||
}
|
||||
|
||||
public class GpsMatcherClient : IGpsMatcherClient
|
||||
{
|
||||
private readonly IMediator _mediator;
|
||||
private readonly ILogger<GpsMatcherClient> _logger;
|
||||
private readonly string _requestAddress;
|
||||
private readonly RequestSocket _requestSocket = new();
|
||||
private readonly string _subscriberAddress;
|
||||
private readonly SubscriberSocket _subscriberSocket = new();
|
||||
private readonly NetMQPoller _poller = new();
|
||||
|
||||
public GpsMatcherClient(IMediator mediator, IOptions<GpsDeniedClientConfig> gpsConfig, ILogger<GpsMatcherClient> logger)
|
||||
{
|
||||
_mediator = mediator;
|
||||
_logger = logger;
|
||||
try
|
||||
{
|
||||
using var process = new Process();
|
||||
process.StartInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = Constants.ExternalGpsDeniedPath,
|
||||
Arguments = $"zeromq --rep {gpsConfig.Value.ZeroMqPort} --pub {gpsConfig.Value.ZeroMqReceiverPort}",
|
||||
WorkingDirectory = Constants.EXTERNAL_GPS_DENIED_FOLDER,
|
||||
CreateNoWindow = true
|
||||
};
|
||||
process.Start();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogError(e, e.ToString());
|
||||
}
|
||||
|
||||
_requestAddress = $"tcp://{gpsConfig.Value.ZeroMqHost}:{gpsConfig.Value.ZeroMqPort}";
|
||||
_requestSocket.Connect(_requestAddress);
|
||||
|
||||
_subscriberAddress = $"tcp://{gpsConfig.Value.ZeroMqHost}:{gpsConfig.Value.ZeroMqReceiverPort}";
|
||||
_subscriberSocket.Connect(_subscriberAddress);
|
||||
_subscriberSocket.Subscribe("");
|
||||
_subscriberSocket.ReceiveReady += async (sender, e) => await ProcessClientCommand(sender, e);
|
||||
|
||||
_poller.Add(_subscriberSocket);
|
||||
_poller.RunAsync();
|
||||
}
|
||||
|
||||
private async Task ProcessClientCommand(object? sender, NetMQSocketEventArgs e)
|
||||
{
|
||||
while (e.Socket.TryReceiveFrameString(TimeSpan.FromMilliseconds(100), out var str))
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrEmpty(str))
|
||||
continue;
|
||||
|
||||
switch (str)
|
||||
{
|
||||
case "FINISHED":
|
||||
await _mediator.Publish(new GPSMatcherFinishedEvent());
|
||||
break;
|
||||
case "OK":
|
||||
await _mediator.Publish(new GPSMatcherJobAcceptedEvent());
|
||||
break;
|
||||
default:
|
||||
var parts = str.Split(',');
|
||||
if (parts.Length != 6)
|
||||
throw new Exception("Matching Result Failed");
|
||||
|
||||
var filename = Path.GetFileNameWithoutExtension(parts[1]);
|
||||
await _mediator.Publish(new GPSMatcherResultEvent
|
||||
{
|
||||
Index = int.Parse(parts[0]),
|
||||
Image = filename,
|
||||
GeoPoint = new GeoPoint(double.Parse(parts[2]), double.Parse(parts[3])),
|
||||
KeyPoints = int.Parse(parts[4]),
|
||||
MatchType = Enum.TryParse<MatchTypeEnum>(parts[5], out var type) ? type : MatchTypeEnum.None
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task StartMatching(StartMatchingEvent e)
|
||||
{
|
||||
_requestSocket.SendFrame(e.ToString());
|
||||
_requestSocket.TryReceiveFrameString(TimeSpan.FromMilliseconds(300), out var response);
|
||||
if (response != "OK")
|
||||
{
|
||||
_logger.LogError(response);
|
||||
await _mediator.Publish(new SetStatusTextEvent(response ?? "", true));
|
||||
}
|
||||
}
|
||||
|
||||
public void Stop() => _requestSocket.SendFrame("STOP");
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_poller.Stop();
|
||||
_poller.Dispose();
|
||||
|
||||
_requestSocket.SendFrame("EXIT");
|
||||
_requestSocket.Disconnect(_requestAddress);
|
||||
_requestSocket.Dispose();
|
||||
|
||||
_subscriberSocket.Disconnect(_subscriberAddress);
|
||||
_subscriberSocket.Dispose();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user