mirror of
https://github.com/azaion/annotations.git
synced 2026-04-22 06:56:29 +00:00
add gps matcher service
This commit is contained in:
@@ -52,6 +52,7 @@ public partial class Annotator
|
||||
|
||||
private readonly TimeSpan _thresholdBefore = TimeSpan.FromMilliseconds(50);
|
||||
private readonly TimeSpan _thresholdAfter = TimeSpan.FromMilliseconds(150);
|
||||
private readonly IGpsMatcherService _gpsMatcherService;
|
||||
private static readonly Guid SaveConfigTaskId = Guid.NewGuid();
|
||||
|
||||
public ObservableCollection<MediaFileInfo> AllMediaFiles { get; set; } = new();
|
||||
@@ -70,7 +71,8 @@ public partial class Annotator
|
||||
ILogger<Annotator> logger,
|
||||
AnnotationService annotationService,
|
||||
IDbFactory dbFactory,
|
||||
IInferenceService inferenceService)
|
||||
IInferenceService inferenceService,
|
||||
IGpsMatcherService gpsMatcherService)
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
@@ -85,6 +87,7 @@ public partial class Annotator
|
||||
_annotationService = annotationService;
|
||||
_dbFactory = dbFactory;
|
||||
_inferenceService = inferenceService;
|
||||
_gpsMatcherService = gpsMatcherService;
|
||||
|
||||
Loaded += OnLoaded;
|
||||
Closed += OnFormClosed;
|
||||
@@ -106,7 +109,7 @@ public partial class Annotator
|
||||
};
|
||||
|
||||
Editor.GetTimeFunc = () => TimeSpan.FromMilliseconds(_mediaPlayer.Time);
|
||||
MapMatcherComponent.Init(_appConfig);
|
||||
MapMatcherComponent.Init(_appConfig, _gpsMatcherService);
|
||||
}
|
||||
|
||||
private void OnLoaded(object sender, RoutedEventArgs e)
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<PackageReference Include="LibVLCSharp" Version="3.9.1" />
|
||||
<PackageReference Include="LibVLCSharp.WPF" Version="3.9.1" />
|
||||
<PackageReference Include="MediatR" Version="12.4.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="9.0.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.0" />
|
||||
|
||||
@@ -24,30 +24,75 @@
|
||||
HorizontalAlignment="Stretch"
|
||||
Grid.Column="0">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="28"></RowDefinition>
|
||||
<RowDefinition Height="28"></RowDefinition>
|
||||
<RowDefinition Height="28"></RowDefinition>
|
||||
<RowDefinition Height="*"></RowDefinition>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="30"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBox
|
||||
Grid.Column="0"
|
||||
<Grid
|
||||
Grid.Row="0"
|
||||
HorizontalAlignment="Stretch"
|
||||
Margin="1"
|
||||
x:Name="TbGpsMapFolder"></TextBox>
|
||||
<Button
|
||||
Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
Margin="1"
|
||||
Click="OpenGpsTilesFolderClick">
|
||||
. . .
|
||||
</Button>
|
||||
<ListView Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="2"
|
||||
HorizontalAlignment="Stretch">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"></ColumnDefinition>
|
||||
<ColumnDefinition Width="32"></ColumnDefinition>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBox
|
||||
Grid.Column="0"
|
||||
Grid.Row="0"
|
||||
HorizontalAlignment="Stretch"
|
||||
Margin="1"
|
||||
x:Name="TbGpsMapFolder"></TextBox>
|
||||
<Button
|
||||
Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
Margin="1"
|
||||
Click="OpenGpsTilesFolderClick">
|
||||
. . .
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
<Grid
|
||||
Grid.Row="1"
|
||||
HorizontalAlignment="Stretch">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="50"></ColumnDefinition>
|
||||
<ColumnDefinition Width="*"></ColumnDefinition>
|
||||
</Grid.ColumnDefinitions>
|
||||
<!-- <TextBlock -->
|
||||
<!-- Grid.Column="0" -->
|
||||
<!-- Text="Lat" -->
|
||||
<!-- Background="Gray"/> -->
|
||||
<Button
|
||||
Grid.Column="0"
|
||||
Margin="1"
|
||||
Click="TestGps">
|
||||
Test
|
||||
</Button>
|
||||
<TextBox
|
||||
Grid.Column="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
x:Name="TbLat"
|
||||
Text="48.2748909"></TextBox>
|
||||
</Grid>
|
||||
<Grid
|
||||
Grid.Row="2"
|
||||
HorizontalAlignment="Stretch">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="50"></ColumnDefinition>
|
||||
<ColumnDefinition Width="*"></ColumnDefinition>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock
|
||||
Grid.Column="0"
|
||||
Text="Lon"
|
||||
Background="Gray"/>
|
||||
<TextBox
|
||||
Grid.Column="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
x:Name="TbLon"
|
||||
Text="37.3834877"></TextBox>
|
||||
</Grid>
|
||||
<ListView Grid.Row="3"
|
||||
Name="GpsFiles"
|
||||
Background="Black"
|
||||
SelectedItem="{Binding Path=SelectedVideo}"
|
||||
|
||||
@@ -11,6 +11,7 @@ using Azaion.Common.Database;
|
||||
using Azaion.Common.DTO;
|
||||
using Azaion.Common.DTO.Config;
|
||||
using Azaion.Common.Extensions;
|
||||
using Azaion.Common.Services;
|
||||
using GMap.NET;
|
||||
using GMap.NET.MapProviders;
|
||||
using GMap.NET.WindowsPresentation;
|
||||
@@ -22,17 +23,19 @@ public partial class MapMatcher : UserControl
|
||||
{
|
||||
private AppConfig _appConfig;
|
||||
List<MediaFileInfo> _allMediaFiles;
|
||||
private Dictionary<string, Annotation> _annotations;
|
||||
private Dictionary<int, Annotation> _annotations;
|
||||
private string _currentDir;
|
||||
private IGpsMatcherService _gpsMatcherService;
|
||||
|
||||
public MapMatcher()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public void Init(AppConfig appConfig)
|
||||
public void Init(AppConfig appConfig, IGpsMatcherService gpsMatcherService)
|
||||
{
|
||||
_appConfig = appConfig;
|
||||
_gpsMatcherService = gpsMatcherService;
|
||||
GoogleMapProvider.Instance.ApiKey = appConfig.MapConfig.ApiKey;
|
||||
SatelliteMap.MapProvider = GMapProviders.GoogleSatelliteMap;
|
||||
SatelliteMap.Position = new PointLatLng(48.295985271707664, 37.14477539062501);
|
||||
@@ -44,7 +47,7 @@ public partial class MapMatcher : UserControl
|
||||
private async Task OpenGpsLocation(int gpsFilesIndex)
|
||||
{
|
||||
var media = GpsFiles.Items[gpsFilesIndex] as MediaFileInfo;
|
||||
var ann = _annotations.GetValueOrDefault(Path.GetFileNameWithoutExtension(media.Name));
|
||||
var ann = _annotations.GetValueOrDefault(gpsFilesIndex);
|
||||
GpsImageEditor.Background = new ImageBrush
|
||||
{
|
||||
ImageSource = await Path.Combine(_currentDir, ann.Name).OpenImage()
|
||||
@@ -91,70 +94,63 @@ public partial class MapMatcher : UserControl
|
||||
Path = x.FullName,
|
||||
MediaType = MediaTypes.Image
|
||||
}).ToList();
|
||||
// var allFiles = videoFiles.Concat(imageFiles).ToList();
|
||||
//
|
||||
|
||||
// var labelsDict = await _dbFactory.Run(async db => await db.Annotations
|
||||
// .GroupBy(x => x.Name.Substring(0, x.Name.Length - 7))
|
||||
// .Where(x => allFileNames.Contains(x.Key))
|
||||
// .ToDictionaryAsync(x => x.Key, x => x.Key));
|
||||
//
|
||||
// foreach (var mediaFile in allFiles)
|
||||
// mediaFile.HasAnnotations = labelsDict.ContainsKey(mediaFile.FName);
|
||||
//
|
||||
// AllMediaFiles = new ObservableCollection<MediaFileInfo>(allFiles);
|
||||
|
||||
_allMediaFiles = mediaFiles;
|
||||
GpsFiles.ItemsSource = new ObservableCollection<MediaFileInfo>(_allMediaFiles);
|
||||
var annotations = SetFromCsv(mediaFiles);
|
||||
Cursor = Cursors.Wait;
|
||||
await Task.Delay(TimeSpan.FromSeconds(10));
|
||||
SetMarkers(annotations);
|
||||
Cursor = Cursors.Arrow;
|
||||
await OpenGpsLocation(0);
|
||||
}
|
||||
|
||||
private Dictionary<string, Annotation> SetFromCsv(List<MediaFileInfo> mediaFiles)
|
||||
{
|
||||
_annotations = mediaFiles.Select(x => new Annotation
|
||||
_annotations = mediaFiles.Select((x, i) => (i, new Annotation
|
||||
{
|
||||
Name = x.Name,
|
||||
OriginalMediaName = x.Name
|
||||
}).ToDictionary(x => Path.GetFileNameWithoutExtension(x.OriginalMediaName));
|
||||
})).ToDictionary(x => x.i, x => x.Item2);
|
||||
|
||||
var csvResults = GpsCsvResult.ReadFromCsv(Constants.CSV_PATH);
|
||||
var initialLat = double.Parse(TbLat.Text);
|
||||
var initialLon = double.Parse(TbLon.Text);
|
||||
|
||||
await _gpsMatcherService.RunGpsMatching(dir.FullName, initialLat, initialLon, async res => await SetMarker(res));
|
||||
}
|
||||
|
||||
private async Task SetMarker(GpsMatchResult result)
|
||||
{
|
||||
await Dispatcher.Invoke(async () =>
|
||||
{
|
||||
var marker = new GMapMarker(new PointLatLng(result.Latitude, result.Longitude));
|
||||
var ann = _annotations[result.Index];
|
||||
marker.Shape = new CircleVisual(marker, System.Windows.Media.Brushes.Blue)
|
||||
{
|
||||
Text = ann.Name
|
||||
};
|
||||
SatelliteMap.Markers.Add(marker);
|
||||
ann.Lat = result.Latitude;
|
||||
ann.Lon = result.Longitude;
|
||||
SatelliteMap.Position = new PointLatLng(result.Latitude, result.Longitude);
|
||||
SatelliteMap.ZoomAndCenterMarkers(null);
|
||||
});
|
||||
}
|
||||
|
||||
private async Task SetFromCsv(List<MediaFileInfo> mediaFiles)
|
||||
{
|
||||
|
||||
var csvResults = GpsMatchResult.ReadFromCsv(Constants.CSV_PATH);
|
||||
var csvDict = csvResults
|
||||
.Where(x => x.MatchType == "stitched")
|
||||
.ToDictionary(x => x.Image);
|
||||
.ToDictionary(x => x.Index);
|
||||
foreach (var ann in _annotations)
|
||||
{
|
||||
var csvRes = csvDict.GetValueOrDefault(ann.Key);
|
||||
if (csvRes == null)
|
||||
continue;
|
||||
|
||||
ann.Value.Lat = csvRes.Latitude;
|
||||
ann.Value.Lon = csvRes.Longitude;
|
||||
await SetMarker(csvRes);
|
||||
}
|
||||
return _annotations;
|
||||
}
|
||||
|
||||
private void SetMarkers(Dictionary<string, Annotation> annotations)
|
||||
private async void TestGps(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!annotations.Any())
|
||||
if (string.IsNullOrEmpty(TbGpsMapFolder.Text))
|
||||
return;
|
||||
|
||||
var firstAnnotation = annotations.FirstOrDefault();
|
||||
SatelliteMap.Position = new PointLatLng(firstAnnotation.Value.Lat, firstAnnotation.Value.Lon);
|
||||
foreach (var ann in annotations.Where(x => x.Value.Lat != 0 && x.Value.Lon != 0))
|
||||
{
|
||||
var marker = new GMapMarker(new PointLatLng(ann.Value.Lat, ann.Value.Lon));
|
||||
var circle = new CircleVisual(marker, System.Windows.Media.Brushes.Blue)
|
||||
{
|
||||
Text = " "
|
||||
};
|
||||
marker.Shape = circle;
|
||||
SatelliteMap.Markers.Add(marker);
|
||||
}
|
||||
SatelliteMap.ZoomAndCenterMarkers(null);
|
||||
var initialLat = double.Parse(TbLat.Text);
|
||||
var initialLon = double.Parse(TbLon.Text);
|
||||
await _gpsMatcherService.RunGpsMatching(TbGpsMapFolder.Text, initialLat, initialLon, async res => await SetMarker(res));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,10 +12,13 @@
|
||||
<PackageReference Include="MediatR" Version="12.4.1" />
|
||||
<PackageReference Include="MessagePack" Version="3.1.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="9.0.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="Polly" Version="8.5.2" />
|
||||
<PackageReference Include="RabbitMQ.Stream.Client" Version="1.8.9" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.7" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="5.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -14,6 +14,8 @@ public class Constants
|
||||
public const string DEFAULT_IMAGES_DIR = "images";
|
||||
public const string DEFAULT_RESULTS_DIR = "results";
|
||||
public const string DEFAULT_THUMBNAILS_DIR = "thumbnails";
|
||||
public const string DEFAULT_GPS_SAT_DIRECTORY = "satellitesDir";
|
||||
public const string DEFAULT_GPS_ROUTE_DIRECTORY = "routeDir";
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
@@ -67,7 +67,9 @@ public class ConfigUpdater : IConfigUpdater
|
||||
ImagesDirectory = Constants.DEFAULT_IMAGES_DIR,
|
||||
LabelsDirectory = Constants.DEFAULT_LABELS_DIR,
|
||||
ResultsDirectory = Constants.DEFAULT_RESULTS_DIR,
|
||||
ThumbnailsDirectory = Constants.DEFAULT_THUMBNAILS_DIR
|
||||
ThumbnailsDirectory = Constants.DEFAULT_THUMBNAILS_DIR,
|
||||
GpsSatDirectory = Constants.DEFAULT_GPS_SAT_DIRECTORY,
|
||||
GpsRouteDirectory = Constants.DEFAULT_GPS_ROUTE_DIRECTORY
|
||||
},
|
||||
|
||||
ThumbnailConfig = new ThumbnailConfig
|
||||
|
||||
@@ -7,4 +7,7 @@ public class DirectoriesConfig
|
||||
public string ImagesDirectory { get; set; } = null!;
|
||||
public string ResultsDirectory { get; set; } = null!;
|
||||
public string ThumbnailsDirectory { get; set; } = null!;
|
||||
|
||||
public string GpsSatDirectory { get; set; } = null!;
|
||||
public string GpsRouteDirectory { get; set; } = null!;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace Azaion.Common.DTO;
|
||||
|
||||
public class DownloadTilesResult
|
||||
{
|
||||
public ConcurrentDictionary<(int x, int y), byte[]> Tiles { get; set; } = null!;
|
||||
public double LatMin { get; set; }
|
||||
public double LatMax { get; set; }
|
||||
public double LonMin { get; set; }
|
||||
public double LonMax { get; set; }
|
||||
}
|
||||
@@ -2,24 +2,25 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
public class GpsCsvResult
|
||||
public class GpsMatchResult
|
||||
{
|
||||
public string Image { get; set; }
|
||||
public double Latitude { get; set; }
|
||||
public int Index { get; set; }
|
||||
public string Image { get; set; }
|
||||
public double Latitude { get; set; }
|
||||
public double Longitude { get; set; }
|
||||
public int Keypoints { get; set; }
|
||||
public int Rotation { get; set; }
|
||||
public string MatchType { get; set; }
|
||||
|
||||
public static List<GpsCsvResult> ReadFromCsv(string csvFilePath)
|
||||
public static List<GpsMatchResult> ReadFromCsv(string csvFilePath)
|
||||
{
|
||||
var imageDatas = new List<GpsCsvResult>();
|
||||
var imageDatas = new List<GpsMatchResult>();
|
||||
|
||||
using var reader = new StreamReader(csvFilePath);
|
||||
//read header
|
||||
reader.ReadLine();
|
||||
if (reader.EndOfStream)
|
||||
return new List<GpsCsvResult>();
|
||||
return new List<GpsMatchResult>();
|
||||
|
||||
while (!reader.EndOfStream)
|
||||
{
|
||||
@@ -29,7 +30,7 @@ public class GpsCsvResult
|
||||
var values = line.Split(',');
|
||||
if (values.Length == 6)
|
||||
{
|
||||
imageDatas.Add(new GpsCsvResult
|
||||
imageDatas.Add(new GpsMatchResult
|
||||
{
|
||||
Image = GetFilename(values[0]),
|
||||
Latitude = double.Parse(values[1]),
|
||||
@@ -0,0 +1,31 @@
|
||||
using Azaion.Common.Extensions;
|
||||
|
||||
namespace Azaion.Common.DTO;
|
||||
|
||||
public class SatTile
|
||||
{
|
||||
public int X { get; }
|
||||
public int Y { get; }
|
||||
public double LeftTopLat { get; }
|
||||
public double LeftTopLon { get; }
|
||||
|
||||
public double BottomRightLat { get; }
|
||||
public double BottomRightLon { get; }
|
||||
public string Url { get; set; }
|
||||
|
||||
|
||||
public SatTile(int x, int y, int zoom, string url)
|
||||
{
|
||||
X = x;
|
||||
Y = y;
|
||||
Url = url;
|
||||
|
||||
(LeftTopLat, LeftTopLon) = GeoUtils.TileToWorldPos(x, y, zoom);
|
||||
(BottomRightLat, BottomRightLon) = GeoUtils.TileToWorldPos(x + 1, y + 1, zoom);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Tile[X={X}, Y={Y}, TL=({LeftTopLat:F6}, {LeftTopLon:F6}), BR=({BottomRightLat:F6}, {BottomRightLon:F6})]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
namespace Azaion.Common.Extensions;
|
||||
|
||||
public static class GeoUtils
|
||||
{
|
||||
private const double EARTH_RADIUS = 6378137;
|
||||
|
||||
public static (int x, int y) WorldToTilePos(double lat, double lon, int zoom)
|
||||
{
|
||||
var latRad = lat * Math.PI / 180.0;
|
||||
var n = Math.Pow(2.0, zoom);
|
||||
var xTile = (int)Math.Floor((lon + 180.0) / 360.0 * n);
|
||||
var yTile = (int)Math.Floor((1.0 - Math.Log(Math.Tan(latRad) + 1.0 / Math.Cos(latRad)) / Math.PI) / 2.0 * n);
|
||||
return (xTile, yTile);
|
||||
}
|
||||
|
||||
public static (double lat, double lon) TileToWorldPos(int x, int y, int zoom)
|
||||
{
|
||||
var n = Math.Pow(2.0, zoom);
|
||||
var lonDeg = x / n * 360.0 - 180.0;
|
||||
var latRad = Math.Atan(Math.Sinh(Math.PI * (1.0 - 2.0 * y / n)));
|
||||
var latDeg = latRad * 180.0 / Math.PI;
|
||||
return (latDeg, lonDeg);
|
||||
}
|
||||
|
||||
public static (double minLat, double maxLat, double minLon, double maxLon) GetBoundingBox(double centerLat, double centerLon, double radiusM)
|
||||
{
|
||||
var latRad = centerLat * Math.PI / 180.0;
|
||||
|
||||
var latDiff = (radiusM / EARTH_RADIUS) * (180.0 / Math.PI);
|
||||
var minLat = Math.Max(centerLat - latDiff, -90.0);
|
||||
var maxLat = Math.Min(centerLat + latDiff, 90.0);
|
||||
|
||||
var lonDiff = (radiusM / (EARTH_RADIUS * Math.Cos(latRad))) * (180.0 / Math.PI);
|
||||
var minLon = Math.Max(centerLon - lonDiff, -180.0);
|
||||
var maxLon = Math.Min(centerLon + lonDiff, 180.0);
|
||||
|
||||
return (minLat, maxLat, minLon, maxLon);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using Azaion.Common.DTO;
|
||||
using Azaion.Common.DTO.Config;
|
||||
using Azaion.CommonSecurity;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Azaion.Common.Services;
|
||||
|
||||
public interface IGpsMatcherService
|
||||
{
|
||||
Task RunGpsMatching(string userRouteDir, double initialLatitude, double initialLongitude, Func<GpsMatchResult, Task> processResult, CancellationToken detectToken = default);
|
||||
void StopGpsMatching();
|
||||
}
|
||||
|
||||
public class GpsMatcherService(IGpsMatcherClient gpsMatcherClient, ISatelliteDownloader satelliteTileDownloader, IOptions<DirectoriesConfig> dirConfig) : IGpsMatcherService
|
||||
{
|
||||
private const int ZOOM_LEVEL = 18;
|
||||
private const int POINTS_COUNT = 10;
|
||||
private const int DISTANCE_BETWEEN_POINTS_M = 100;
|
||||
private const double SATELLITE_RADIUS_M = DISTANCE_BETWEEN_POINTS_M * (POINTS_COUNT + 1);
|
||||
|
||||
public async Task RunGpsMatching(string userRouteDir, double initialLatitude, double initialLongitude, Func<GpsMatchResult, Task> processResult, CancellationToken detectToken = default)
|
||||
{
|
||||
var currentLat = initialLatitude;
|
||||
var currentLon = initialLongitude;
|
||||
|
||||
var routeDir = Path.Combine(SecurityConstants.EXTERNAL_GPS_DENIED_FOLDER, dirConfig.Value.GpsRouteDirectory);
|
||||
if (Directory.Exists(routeDir))
|
||||
Directory.Delete(routeDir, true);
|
||||
Directory.CreateDirectory(routeDir);
|
||||
|
||||
var routeFiles = new List<string>();
|
||||
foreach (var file in Directory.GetFiles(userRouteDir))
|
||||
{
|
||||
routeFiles.Add(file);
|
||||
File.Copy(file, Path.Combine(routeDir, Path.GetFileName(file)));
|
||||
}
|
||||
|
||||
var indexOffset = 0;
|
||||
while (routeFiles.Any())
|
||||
{
|
||||
await satelliteTileDownloader.GetTiles(currentLat, currentLon, SATELLITE_RADIUS_M, ZOOM_LEVEL, detectToken);
|
||||
gpsMatcherClient.StartMatching(new StartMatchingEvent
|
||||
{
|
||||
ImagesCount = POINTS_COUNT,
|
||||
Latitude = initialLatitude,
|
||||
Longitude = initialLongitude,
|
||||
SatelliteImagesDir = dirConfig.Value.GpsSatDirectory,
|
||||
RouteDir = dirConfig.Value.GpsRouteDirectory
|
||||
});
|
||||
|
||||
while (true)
|
||||
{
|
||||
var result = gpsMatcherClient.GetResult();
|
||||
if (result == null)
|
||||
break;
|
||||
result.Index += indexOffset;
|
||||
await processResult(result);
|
||||
currentLat = result.Latitude;
|
||||
currentLon = result.Longitude;
|
||||
routeFiles.RemoveAt(0);
|
||||
}
|
||||
indexOffset += POINTS_COUNT;
|
||||
}
|
||||
}
|
||||
|
||||
public void StopGpsMatching()
|
||||
{
|
||||
gpsMatcherClient.Stop();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
using System.Diagnostics;
|
||||
using Azaion.Common.DTO;
|
||||
using Azaion.CommonSecurity;
|
||||
using Azaion.CommonSecurity.DTO;
|
||||
using Microsoft.Extensions.Options;
|
||||
using NetMQ;
|
||||
using NetMQ.Sockets;
|
||||
|
||||
namespace Azaion.Common.Services;
|
||||
|
||||
public interface IGpsMatcherClient
|
||||
{
|
||||
|
||||
void StartMatching(StartMatchingEvent startEvent);
|
||||
GpsMatchResult? GetResult(int retries = 2, int tryTimeoutSeconds = 5, CancellationToken ct = default);
|
||||
void Stop();
|
||||
}
|
||||
|
||||
public class StartMatchingEvent
|
||||
{
|
||||
public string RouteDir { get; set; } = null!;
|
||||
public string SatelliteImagesDir { get; set; } = null!;
|
||||
public int ImagesCount { get; set; }
|
||||
public double Latitude { get; set; }
|
||||
public double Longitude { get; set; }
|
||||
public string ProcessingType { get; set; } = "cuda";
|
||||
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},{Latitude},{Longitude},{ProcessingType},{Altitude},{CameraSensorWidth},{CameraFocalLength}";
|
||||
}
|
||||
|
||||
public class GpsMatcherClient : IGpsMatcherClient
|
||||
{
|
||||
private readonly GpsDeniedClientConfig _gpsDeniedClientConfig;
|
||||
private readonly RequestSocket _requestSocket = new();
|
||||
private readonly SubscriberSocket _subscriberSocket = new();
|
||||
|
||||
public GpsMatcherClient(IOptions<GpsDeniedClientConfig> gpsDeniedClientConfig)
|
||||
{
|
||||
_gpsDeniedClientConfig = gpsDeniedClientConfig.Value;
|
||||
Start();
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
try
|
||||
{
|
||||
using var process = new Process();
|
||||
process.StartInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = SecurityConstants.ExternalGpsDeniedPath,
|
||||
WorkingDirectory = SecurityConstants.EXTERNAL_GPS_DENIED_FOLDER
|
||||
//Arguments = $"-e {credentials.Email} -p {credentials.Password} -f {apiConfig.ResourcesFolder}",
|
||||
//RedirectStandardOutput = true,
|
||||
//RedirectStandardError = true,
|
||||
//CreateNoWindow = true
|
||||
};
|
||||
|
||||
process.OutputDataReceived += (_, e) => { if (e.Data != null) Console.WriteLine(e.Data); };
|
||||
process.ErrorDataReceived += (_, e) => { if (e.Data != null) Console.WriteLine(e.Data); };
|
||||
//process.Start();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e);
|
||||
//throw;
|
||||
}
|
||||
_requestSocket.Connect($"tcp://{_gpsDeniedClientConfig.ZeroMqHost}:{_gpsDeniedClientConfig.ZeroMqPort}");
|
||||
_subscriberSocket.Connect($"tcp://{_gpsDeniedClientConfig.ZeroMqHost}:{_gpsDeniedClientConfig.ZeroMqSubscriberPort}");
|
||||
_subscriberSocket.Subscribe("");
|
||||
}
|
||||
|
||||
public void StartMatching(StartMatchingEvent e)
|
||||
{
|
||||
_requestSocket.SendFrame(e.ToString());
|
||||
var response = _requestSocket.ReceiveFrameString();
|
||||
if (response != "OK")
|
||||
throw new Exception("Start Matching Failed");
|
||||
}
|
||||
|
||||
public GpsMatchResult? GetResult(int retries = 15, int tryTimeoutSeconds = 5, CancellationToken ct = default)
|
||||
{
|
||||
var tryNum = 0;
|
||||
while (!ct.IsCancellationRequested && tryNum++ < retries)
|
||||
{
|
||||
if (!_subscriberSocket.TryReceiveFrameString(TimeSpan.FromSeconds(tryTimeoutSeconds), out var update))
|
||||
continue;
|
||||
if (update == "FINISHED")
|
||||
return null;
|
||||
|
||||
var parts = update.Split(',');
|
||||
if (parts.Length != 5)
|
||||
throw new Exception("Matching Result Failed");
|
||||
|
||||
return new GpsMatchResult
|
||||
{
|
||||
Index = int.Parse(parts[0]),
|
||||
Image = parts[1],
|
||||
Latitude = double.Parse(parts[2]),
|
||||
Longitude = double.Parse(parts[3]),
|
||||
MatchType = parts[4]
|
||||
};
|
||||
}
|
||||
|
||||
if (!ct.IsCancellationRequested)
|
||||
throw new Exception($"Unable to get bytes after {tryNum} retries, {tryTimeoutSeconds} seconds each");
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
_requestSocket.SendFrame("STOP");
|
||||
}
|
||||
}
|
||||
@@ -17,20 +17,20 @@ public interface IInferenceService
|
||||
void StopInference();
|
||||
}
|
||||
|
||||
public class InferenceService(ILogger<InferenceService> logger, [FromKeyedServices(SecurityConstants.EXTERNAL_INFERENCE_PATH)] IExternalClient externalClient, IOptions<AIRecognitionConfig> aiConfigOptions) : IInferenceService
|
||||
public class InferenceService(ILogger<InferenceService> logger, IInferenceClient client, IOptions<AIRecognitionConfig> aiConfigOptions) : IInferenceService
|
||||
{
|
||||
public async Task RunInference(List<string> mediaPaths, Func<AnnotationImage, Task> processAnnotation, CancellationToken detectToken = default)
|
||||
{
|
||||
var aiConfig = aiConfigOptions.Value;
|
||||
|
||||
aiConfig.Paths = mediaPaths;
|
||||
externalClient.Send(RemoteCommand.Create(CommandType.Inference, aiConfig));
|
||||
client.Send(RemoteCommand.Create(CommandType.Inference, aiConfig));
|
||||
|
||||
while (!detectToken.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
var bytes = externalClient.GetBytes(ct: detectToken);
|
||||
var bytes = client.GetBytes(ct: detectToken);
|
||||
if (bytes == null)
|
||||
throw new Exception("Can't get bytes from inference client");
|
||||
|
||||
@@ -51,6 +51,6 @@ public class InferenceService(ILogger<InferenceService> logger, [FromKeyedServic
|
||||
|
||||
public void StopInference()
|
||||
{
|
||||
externalClient.Send(RemoteCommand.Create(CommandType.StopInference));
|
||||
client.Send(RemoteCommand.Create(CommandType.StopInference));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,290 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Json;
|
||||
using Azaion.Common.DTO;
|
||||
using Azaion.Common.DTO.Config;
|
||||
using Azaion.Common.Extensions;
|
||||
using Azaion.CommonSecurity;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Newtonsoft.Json;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using SixLabors.ImageSharp.Processing;
|
||||
|
||||
namespace Azaion.Common.Services;
|
||||
|
||||
public interface ISatelliteDownloader
|
||||
{
|
||||
Task GetTiles(double latitude, double longitude, double radiusM, int zoomLevel, CancellationToken token = default);
|
||||
}
|
||||
|
||||
public class SatelliteDownloader(
|
||||
ILogger<SatelliteDownloader> logger,
|
||||
IOptions<MapConfig> mapConfig,
|
||||
IOptions<DirectoriesConfig> directoriesConfig,
|
||||
IHttpClientFactory httpClientFactory)
|
||||
: ISatelliteDownloader
|
||||
{
|
||||
private const int INPUT_TILE_SIZE = 256;
|
||||
|
||||
private const string TILE_URL_TEMPLATE = "https://mt{0}.google.com/vt/lyrs=s&x={1}&y={2}&z={3}&token={4}";
|
||||
private const int NUM_SERVERS = 4;
|
||||
|
||||
private const int CROP_WIDTH = 1024;
|
||||
private const int CROP_HEIGHT = 1024;
|
||||
private const int STEP_X = 300;
|
||||
private const int STEP_Y = 300;
|
||||
private const int OUTPUT_TILE_SIZE = 512;
|
||||
|
||||
private readonly string _apiKey = mapConfig.Value.ApiKey;
|
||||
private readonly string _satDirectory = Path.Combine(SecurityConstants.EXTERNAL_GPS_DENIED_FOLDER, directoriesConfig.Value.GpsSatDirectory);
|
||||
|
||||
public async Task GetTiles(double centerLat, double centerLon, double radiusM, int zoomLevel, CancellationToken token = default)
|
||||
{
|
||||
//empty Satellite directory
|
||||
if (Directory.Exists(_satDirectory))
|
||||
Directory.Delete(_satDirectory, true);
|
||||
Directory.CreateDirectory(_satDirectory);
|
||||
|
||||
var downloadTilesResult = await DownloadTiles(centerLat, centerLon, radiusM, zoomLevel, token);
|
||||
var image = await ComposeTiles(downloadTilesResult.Tiles, token);
|
||||
if (image != null)
|
||||
await SplitToTiles(image, downloadTilesResult, token);
|
||||
}
|
||||
|
||||
private async Task SplitToTiles(Image<Rgba32> image, DownloadTilesResult bounds, CancellationToken token = default)
|
||||
{
|
||||
// Calculate all crop parameters beforehand
|
||||
var cropTasks = new List<Action>();
|
||||
var latRange = bounds.LatMax - bounds.LatMin; // [cite: 13]
|
||||
var lonRange = bounds.LonMax - bounds.LonMin; // [cite: 13]
|
||||
var degreesPerPixelLat = latRange / image.Height; // [cite: 13]
|
||||
var degreesPerPixelLon = lonRange / image.Width; // [cite: 14]
|
||||
int tempRowIndex = 0;
|
||||
|
||||
|
||||
for (int top = 0; top <= image.Height - CROP_HEIGHT; top += STEP_Y) // [cite: 15]
|
||||
{
|
||||
int tempColIndex = 0;
|
||||
for (int left = 0; left <= image.Width - CROP_WIDTH; left += STEP_X) // [cite: 16]
|
||||
{
|
||||
// Capture loop variables for the closure
|
||||
int currentTop = top;
|
||||
int currentLeft = left;
|
||||
int rowIndex = tempRowIndex;
|
||||
int colIndex = tempColIndex;
|
||||
|
||||
cropTasks.Add(() =>
|
||||
{
|
||||
token.ThrowIfCancellationRequested();
|
||||
var cropBox = new Rectangle(currentLeft, currentTop, CROP_WIDTH, CROP_HEIGHT);
|
||||
|
||||
using var croppedImage = image.Clone(ctx => ctx.Crop(cropBox));
|
||||
var cropTlLat = bounds.LatMax - (currentTop * degreesPerPixelLat);
|
||||
var cropTlLon = bounds.LonMin + (currentLeft * degreesPerPixelLon);
|
||||
var cropBrLat = cropTlLat - (CROP_HEIGHT * degreesPerPixelLat);
|
||||
var cropBrLon = cropTlLon + (CROP_WIDTH * degreesPerPixelLon);
|
||||
|
||||
var outputFilename = Path.Combine(_satDirectory,
|
||||
$"map_{rowIndex:D4}_{colIndex:D4}_tl_{cropTlLat:F6}_{cropTlLon:F6}_br_{cropBrLat:F6}_{cropBrLon:F6}.tif"
|
||||
);
|
||||
|
||||
using var resizedImage = croppedImage.Clone(ctx => ctx.Resize(OUTPUT_TILE_SIZE, OUTPUT_TILE_SIZE, KnownResamplers.Lanczos3));
|
||||
resizedImage.SaveAsTiffAsync(outputFilename, token).GetAwaiter().GetResult(); // Use synchronous saving or manage async Tasks properly in parallel context
|
||||
});
|
||||
tempColIndex++;
|
||||
}
|
||||
tempRowIndex++;
|
||||
}
|
||||
// Execute tasks in parallel
|
||||
await Task.Run(() => Parallel.ForEach(cropTasks, action => action()), token);
|
||||
}
|
||||
|
||||
private async Task SplitToTiles_OLD(Image<Rgba32> image, DownloadTilesResult bounds, CancellationToken token = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(image);
|
||||
ArgumentNullException.ThrowIfNull(bounds);
|
||||
|
||||
if (bounds.LatMax <= bounds.LatMin || bounds.LonMax <= bounds.LonMin || image.Width <= 0 || image.Height <= 0)
|
||||
throw new ArgumentException("Invalid coordinate bounds (LatMax <= LatMin or LonMax <= LonMin) or image dimensions (Width/Height <= 0).");
|
||||
|
||||
var latRange = bounds.LatMax - bounds.LatMin;
|
||||
var lonRange = bounds.LonMax - bounds.LonMin;
|
||||
var degreesPerPixelLat = latRange / image.Height;
|
||||
var degreesPerPixelLon = lonRange / image.Width;
|
||||
|
||||
var rowIndex = 0;
|
||||
for (int top = 0; top <= image.Height - CROP_HEIGHT; top += STEP_Y)
|
||||
{
|
||||
token.ThrowIfCancellationRequested();
|
||||
int colIndex = 0;
|
||||
for (int left = 0; left <= image.Width - CROP_WIDTH; left += STEP_X)
|
||||
{
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
var cropBox = new Rectangle(left, top, CROP_WIDTH, CROP_HEIGHT);
|
||||
|
||||
using (var croppedImage = image.Clone(ctx => ctx.Crop(cropBox)))
|
||||
{
|
||||
var cropTlLat = bounds.LatMax - (top * degreesPerPixelLat);
|
||||
var cropTlLon = bounds.LonMin + (left * degreesPerPixelLon);
|
||||
var cropBrLat = cropTlLat - (CROP_HEIGHT * degreesPerPixelLat);
|
||||
var cropBrLon = cropTlLon + (CROP_WIDTH * degreesPerPixelLon);
|
||||
|
||||
var outputFilename = Path.Combine(_satDirectory,
|
||||
$"map_{rowIndex:D4}_{colIndex:D4}_tl_{cropTlLat:F6}_{cropTlLon:F6}_br_{cropBrLat:F6}_{cropBrLon:F6}.tif"
|
||||
);
|
||||
|
||||
using (var resizedImage = croppedImage.Clone(ctx => ctx.Resize(OUTPUT_TILE_SIZE, OUTPUT_TILE_SIZE, KnownResamplers.Lanczos3)))
|
||||
await resizedImage.SaveAsTiffAsync(outputFilename, token);
|
||||
}
|
||||
colIndex++;
|
||||
}
|
||||
rowIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private async Task<Image<Rgba32>?> ComposeTiles(ConcurrentDictionary<(int x, int y), byte[]> downloadedTiles, CancellationToken token = default)
|
||||
{
|
||||
if (downloadedTiles.IsEmpty)
|
||||
return null;
|
||||
|
||||
var xMin = downloadedTiles.Min(t => t.Key.x);
|
||||
var xMax = downloadedTiles.Max(t => t.Key.x);
|
||||
var yMin = downloadedTiles.Min(t => t.Key.y);
|
||||
var yMax = downloadedTiles.Max(t => t.Key.y);
|
||||
|
||||
var totalWidth = (xMax - xMin + 1) * INPUT_TILE_SIZE;
|
||||
var totalHeight = (yMax - yMin + 1) * INPUT_TILE_SIZE;
|
||||
|
||||
if (totalWidth <= 0 || totalHeight <= 0)
|
||||
return null;
|
||||
|
||||
var largeImage = new Image<Rgba32>(totalWidth, totalHeight);
|
||||
|
||||
largeImage.Mutate(ctx =>
|
||||
{
|
||||
for (var y = yMin; y <= yMax; y++)
|
||||
{
|
||||
for (var x = xMin; x <= xMax; x++)
|
||||
{
|
||||
if (!downloadedTiles.TryGetValue((x, y), out var tileData))
|
||||
continue;
|
||||
|
||||
try
|
||||
{
|
||||
using var tileImage = Image.Load(tileData);
|
||||
var offsetX = (x - xMin) * INPUT_TILE_SIZE;
|
||||
var offsetY = (y - yMin) * INPUT_TILE_SIZE;
|
||||
ctx.DrawImage(tileImage, new Point(offsetX, offsetY), 1f);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
Console.WriteLine($"Error while loading tile: {tileData}");
|
||||
}
|
||||
if (token.IsCancellationRequested)
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// await largeImage.SaveAsync(Path.Combine(_satDirectory, "full_map.tif"),
|
||||
// new TiffEncoder { Compression = TiffCompression.Deflate }, token);
|
||||
return largeImage;
|
||||
}
|
||||
|
||||
private record SessionResponse(string Session);
|
||||
|
||||
private async Task<string?> GetSessionToken()
|
||||
{
|
||||
var url = $"https://tile.googleapis.com/v1/createSession?key={_apiKey}";
|
||||
using var httpClient = httpClientFactory.CreateClient();
|
||||
try
|
||||
{
|
||||
var str = JsonConvert.SerializeObject(new { mapType = "satellite" });
|
||||
var response = await httpClient.PostAsync(url, new StringContent(str));
|
||||
response.EnsureSuccessStatusCode();
|
||||
var sessionResponse = await response.Content.ReadFromJsonAsync<SessionResponse>();
|
||||
return sessionResponse?.Session;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.LogError(e, e.Message);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<DownloadTilesResult> DownloadTiles(double centerLat, double centerLon, double radiusM, int zoomLevel, CancellationToken token = default)
|
||||
{
|
||||
var (latMin, latMax, lonMin, lonMax) = GeoUtils.GetBoundingBox(centerLat, centerLon, radiusM);
|
||||
|
||||
var (xMin, yMin) = GeoUtils.WorldToTilePos(latMax, lonMin, zoomLevel); // Top-left corner
|
||||
var (xMax, yMax) = GeoUtils.WorldToTilePos(latMin, lonMax, zoomLevel); // Bottom-right corner
|
||||
|
||||
var tilesToDownload = new ConcurrentQueue<SatTile>();
|
||||
var downloadedTiles = new ConcurrentDictionary<(int x, int y), byte[]>();
|
||||
var server = 0;
|
||||
var sessionToken = await GetSessionToken();
|
||||
|
||||
for (var y = yMin; y <= yMax + 1; y++)
|
||||
for (var x = xMin; x <= xMax + 1; x++)
|
||||
{
|
||||
token.ThrowIfCancellationRequested();
|
||||
var url = string.Format(TILE_URL_TEMPLATE, server, x, y, zoomLevel, sessionToken);
|
||||
|
||||
tilesToDownload.Enqueue(new SatTile(x, y, zoomLevel, url));
|
||||
server = (server + 1) % NUM_SERVERS;
|
||||
}
|
||||
|
||||
var downloadTasks = new List<Task>();
|
||||
int downloadedCount = 0;
|
||||
|
||||
|
||||
for (int i = 0; i < NUM_SERVERS; i++)
|
||||
{
|
||||
downloadTasks.Add(Task.Run(async () =>
|
||||
{
|
||||
using var httpClient = httpClientFactory.CreateClient();
|
||||
|
||||
while (tilesToDownload.TryDequeue(out var tileInfo))
|
||||
{
|
||||
if (token.IsCancellationRequested) break;
|
||||
try
|
||||
{
|
||||
httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.0.0 Safari/537.36");
|
||||
var response = await httpClient.GetAsync(tileInfo.Url, token);
|
||||
response.EnsureSuccessStatusCode();
|
||||
var tileData = await response.Content.ReadAsByteArrayAsync(token);
|
||||
if (tileData?.Length > 0)
|
||||
{
|
||||
downloadedTiles.TryAdd((tileInfo.X, tileInfo.Y), tileData);
|
||||
Interlocked.Increment(ref downloadedCount);
|
||||
}
|
||||
}
|
||||
catch (HttpRequestException requestException)
|
||||
{
|
||||
logger.LogError(requestException, $"Fail to download tile! Url: {tileInfo.Url}. {requestException.Message}");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.LogError(e, $"Fail to download tile! {e.Message}");
|
||||
}
|
||||
}
|
||||
}, token));
|
||||
}
|
||||
|
||||
await Task.WhenAll(downloadTasks);
|
||||
return new DownloadTilesResult
|
||||
{
|
||||
Tiles = downloadedTiles,
|
||||
LatMin = latMin,
|
||||
LatMax = latMax,
|
||||
LonMin = lonMin,
|
||||
LonMax = lonMax
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -15,5 +15,5 @@ public class InferenceClientConfig : ExternalClientConfig
|
||||
|
||||
public class GpsDeniedClientConfig : ExternalClientConfig
|
||||
{
|
||||
public int ZeroMqReceiverPort { get; set; }
|
||||
public int ZeroMqSubscriberPort { get; set; }
|
||||
}
|
||||
@@ -9,7 +9,6 @@ public enum RoleEnum
|
||||
Validator = 20, //annotator + dataset explorer. This role allows to receive annotations from the queue.
|
||||
CompanionPC = 30,
|
||||
Admin = 40, //
|
||||
ResourceUploader = 50, //Uploading dll and ai models
|
||||
ApiAdmin = 1000 //everything
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,8 @@ public class SecurityConstants
|
||||
|
||||
#region ExternalClientsConfig
|
||||
public const string EXTERNAL_INFERENCE_PATH = "azaion-inference.exe";
|
||||
public const string EXTERNAL_GPS_DENIED_PATH = "image-matcher.exe";
|
||||
public const string EXTERNAL_GPS_DENIED_FOLDER = "gps-denied";
|
||||
public static readonly string ExternalGpsDeniedPath = Path.Combine(EXTERNAL_GPS_DENIED_FOLDER, "image-matcher.exe");
|
||||
|
||||
public const string DEFAULT_ZMQ_INFERENCE_HOST = "127.0.0.1";
|
||||
public const int DEFAULT_ZMQ_INFERENCE_PORT = 5227;
|
||||
|
||||
@@ -10,14 +10,14 @@ public interface IAuthProvider
|
||||
User CurrentUser { get; }
|
||||
}
|
||||
|
||||
public class AuthProvider([FromKeyedServices(SecurityConstants.EXTERNAL_INFERENCE_PATH)] IExternalClient externalClient) : IAuthProvider
|
||||
public class AuthProvider(IInferenceClient inferenceClient) : IAuthProvider
|
||||
{
|
||||
public User CurrentUser { get; private set; } = null!;
|
||||
|
||||
public void Login(ApiCredentials credentials)
|
||||
{
|
||||
externalClient.Send(RemoteCommand.Create(CommandType.Login, credentials));
|
||||
var user = externalClient.Get<User>();
|
||||
inferenceClient.Send(RemoteCommand.Create(CommandType.Login, credentials));
|
||||
var user = inferenceClient.Get<User>();
|
||||
if (user == null)
|
||||
throw new Exception("Can't get user from Auth provider");
|
||||
|
||||
|
||||
+11
-24
@@ -9,27 +9,23 @@ using NetMQ.Sockets;
|
||||
|
||||
namespace Azaion.CommonSecurity.Services;
|
||||
|
||||
public interface IExternalClient
|
||||
public interface IInferenceClient
|
||||
{
|
||||
void Stop();
|
||||
|
||||
void Send(RemoteCommand create);
|
||||
T? Get<T>(int retries = 24, int tryTimeoutSeconds = 5, CancellationToken ct = default) where T : class;
|
||||
byte[]? GetBytes(int retries = 24, int tryTimeoutSeconds = 5, CancellationToken ct = default);
|
||||
void Stop();
|
||||
}
|
||||
|
||||
public abstract class BaseZeroMqExternalClient : IExternalClient
|
||||
public class InferenceClient : IInferenceClient
|
||||
{
|
||||
private readonly DealerSocket _dealer = new();
|
||||
private readonly Guid _clientId = Guid.NewGuid();
|
||||
private readonly InferenceClientConfig _inferenceClientConfig;
|
||||
|
||||
private readonly ExternalClientConfig _externalClientConfig;
|
||||
|
||||
protected abstract string ClientPath { get; }
|
||||
|
||||
protected BaseZeroMqExternalClient(ExternalClientConfig config)
|
||||
public InferenceClient(IOptions<InferenceClientConfig> config)
|
||||
{
|
||||
_externalClientConfig = config;
|
||||
_inferenceClientConfig = config.Value;
|
||||
Start();
|
||||
}
|
||||
|
||||
@@ -40,7 +36,7 @@ public abstract class BaseZeroMqExternalClient : IExternalClient
|
||||
using var process = new Process();
|
||||
process.StartInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = ClientPath,
|
||||
FileName = SecurityConstants.EXTERNAL_INFERENCE_PATH,
|
||||
//Arguments = $"-e {credentials.Email} -p {credentials.Password} -f {apiConfig.ResourcesFolder}",
|
||||
//RedirectStandardOutput = true,
|
||||
//RedirectStandardError = true,
|
||||
@@ -58,7 +54,7 @@ public abstract class BaseZeroMqExternalClient : IExternalClient
|
||||
}
|
||||
|
||||
_dealer.Options.Identity = Encoding.UTF8.GetBytes(_clientId.ToString("N"));
|
||||
_dealer.Connect($"tcp://{_externalClientConfig.ZeroMqHost}:{_externalClientConfig.ZeroMqPort}");
|
||||
_dealer.Connect($"tcp://{_inferenceClientConfig.ZeroMqHost}:{_inferenceClientConfig.ZeroMqPort}");
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
@@ -75,6 +71,9 @@ public abstract class BaseZeroMqExternalClient : IExternalClient
|
||||
_dealer.SendFrame(MessagePackSerializer.Serialize(command));
|
||||
}
|
||||
|
||||
public void SendString(string text) =>
|
||||
Send(new RemoteCommand(CommandType.Load, MessagePackSerializer.Serialize(text)));
|
||||
|
||||
public T? Get<T>(int retries = 24, int tryTimeoutSeconds = 5, CancellationToken ct = default) where T : class
|
||||
{
|
||||
var bytes = GetBytes(retries, tryTimeoutSeconds, ct);
|
||||
@@ -98,15 +97,3 @@ public abstract class BaseZeroMqExternalClient : IExternalClient
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public class InferenceExternalClient(IOptions<InferenceClientConfig> inferenceClientConfig)
|
||||
: BaseZeroMqExternalClient(inferenceClientConfig.Value)
|
||||
{
|
||||
protected override string ClientPath => SecurityConstants.EXTERNAL_INFERENCE_PATH;
|
||||
}
|
||||
|
||||
public class GpsDeniedExternalClient(IOptions<GpsDeniedClientConfig> gpsDeniedClientConfig)
|
||||
: BaseZeroMqExternalClient(gpsDeniedClientConfig.Value)
|
||||
{
|
||||
protected override string ClientPath => SecurityConstants.EXTERNAL_GPS_DENIED_PATH;
|
||||
}
|
||||
@@ -8,12 +8,12 @@ public interface IResourceLoader
|
||||
MemoryStream LoadFile(string fileName, string? folder = null);
|
||||
}
|
||||
|
||||
public class ResourceLoader([FromKeyedServices(SecurityConstants.EXTERNAL_INFERENCE_PATH)] IExternalClient externalClient) : IResourceLoader
|
||||
public class ResourceLoader([FromKeyedServices(SecurityConstants.EXTERNAL_INFERENCE_PATH)] IInferenceClient inferenceClient) : IResourceLoader
|
||||
{
|
||||
public MemoryStream LoadFile(string fileName, string? folder = null)
|
||||
{
|
||||
externalClient.Send(RemoteCommand.Create(CommandType.Load, new LoadFileData(fileName, folder)));
|
||||
var bytes = externalClient.GetBytes();
|
||||
inferenceClient.Send(RemoteCommand.Create(CommandType.Load, new LoadFileData(fileName, folder)));
|
||||
var bytes = inferenceClient.GetBytes();
|
||||
if (bytes == null)
|
||||
throw new Exception($"Unable to receive {fileName}");
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentAssertions" Version="6.12.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
|
||||
<PackageReference Include="Moq" Version="4.20.72" />
|
||||
<PackageReference Include="xunit" Version="2.8.0" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
using Azaion.Common.DTO.Config;
|
||||
using Azaion.Common.Services;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Azaion.Annotator.Test;
|
||||
|
||||
public class GetTilesTestClass
|
||||
{
|
||||
[Fact]
|
||||
public async Task GetTilesTest()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
services.AddHttpClient();
|
||||
var provider = services.BuildServiceProvider();
|
||||
var httpClientFactory = provider.GetService<IHttpClientFactory>()!;
|
||||
|
||||
var satelliteDownloader = new SatelliteDownloader(Mock.Of<ILogger<SatelliteDownloader>>(), new OptionsWrapper<MapConfig>(new MapConfig
|
||||
{
|
||||
Service = "GoogleMaps",
|
||||
ApiKey = "AIzaSyAXRBDBOskC5QOHG6VJWzmVJwYKcu6WH8k"
|
||||
}), new OptionsWrapper<DirectoriesConfig>(new DirectoriesConfig
|
||||
{
|
||||
GpsSatDirectory = "satelliteMaps"
|
||||
}), httpClientFactory);
|
||||
|
||||
await satelliteDownloader.GetTiles(48.2748909, 37.3834877, 600, 18);
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@
|
||||
<PackageReference Include="LibVLCSharp" Version="3.9.1" />
|
||||
<PackageReference Include="LibVLCSharp.WPF" Version="3.9.1" />
|
||||
<PackageReference Include="MediatR" Version="12.4.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="9.0.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.0" />
|
||||
|
||||
+5
-3
@@ -76,9 +76,11 @@ copy logo.ico dist\
|
||||
echo Copying cudnn files
|
||||
set cudnn-folder="C:\Program Files\NVIDIA\CUDNN\v9.4\bin\12.6"
|
||||
copy %cudnn-folder%\* dist\*
|
||||
@REM dont need
|
||||
@REM don't need
|
||||
del dist\cudnn_adv64_9.dll
|
||||
|
||||
cd Azaion.Suite
|
||||
call gps-denied-deploy ..\dist\gps-denied
|
||||
|
||||
echo building installer...
|
||||
iscc build\installer.iss
|
||||
@REM echo building installer...
|
||||
@REM iscc build\installer.iss
|
||||
|
||||
Reference in New Issue
Block a user