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 dirConfig, IOptions 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 = 120; 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 _allRouteImages = new(); private Dictionary _currentRouteImages = new(); private GeoPoint _lastGeoPoint = new(); private CancellationToken _detectToken; private int _currentIndex; private readonly Queue _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); } }