add map support for gps denied

This commit is contained in:
Alex Bezdieniezhnykh
2025-03-17 09:08:43 +02:00
parent 33070b90bf
commit 099f9cf52b
15 changed files with 783 additions and 21 deletions
+348
View File
@@ -0,0 +1,348 @@
using System.Globalization;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Effects;
using GMap.NET.WindowsPresentation;
namespace Azaion.Annotator.Controls
{
public class CircleVisual : FrameworkElement
{
public readonly GMapMarker Marker;
public CircleVisual(GMapMarker m, Brush background)
{
Marker = m;
Marker.ZIndex = 100;
SizeChanged += CircleVisual_SizeChanged;
MouseEnter += CircleVisual_MouseEnter;
MouseLeave += CircleVisual_MouseLeave;
Loaded += OnLoaded;
Text = "?";
StrokeArrow.EndLineCap = PenLineCap.Triangle;
StrokeArrow.LineJoin = PenLineJoin.Round;
RenderTransform = _scale;
Width = Height = 22;
FontSize = Width / 1.55;
Background = background;
Angle = null;
}
void CircleVisual_SizeChanged(object sender, SizeChangedEventArgs e)
{
Marker.Offset = new Point(-e.NewSize.Width / 2, -e.NewSize.Height / 2);
_scale.CenterX = -Marker.Offset.X;
_scale.CenterY = -Marker.Offset.Y;
}
void OnLoaded(object sender, RoutedEventArgs e)
{
UpdateVisual(true);
}
readonly ScaleTransform _scale = new ScaleTransform(1, 1);
void CircleVisual_MouseLeave(object sender, MouseEventArgs e)
{
Marker.ZIndex -= 10000;
Cursor = Cursors.Arrow;
Effect = null;
_scale.ScaleY = 1;
_scale.ScaleX = 1;
}
void CircleVisual_MouseEnter(object sender, MouseEventArgs e)
{
Marker.ZIndex += 10000;
Cursor = Cursors.Hand;
Effect = ShadowEffect;
_scale.ScaleY = 1.5;
_scale.ScaleX = 1.5;
}
public DropShadowEffect ShadowEffect;
static readonly Typeface Font = new Typeface(new FontFamily("Arial"),
FontStyles.Normal,
FontWeights.Bold,
FontStretches.Normal);
FormattedText _fText;
private Brush _background = Brushes.Blue;
public Brush Background
{
get
{
return _background;
}
set
{
if (_background != value)
{
_background = value;
IsChanged = true;
}
}
}
private Brush _foreground = Brushes.White;
public Brush Foreground
{
get
{
return _foreground;
}
set
{
if (_foreground != value)
{
_foreground = value;
IsChanged = true;
ForceUpdateText();
}
}
}
private Pen _stroke = new Pen(Brushes.Blue, 2.0);
public Pen Stroke
{
get
{
return _stroke;
}
set
{
if (_stroke != value)
{
_stroke = value;
IsChanged = true;
}
}
}
private Pen _strokeArrow = new Pen(Brushes.Blue, 2.0);
public Pen StrokeArrow
{
get
{
return _strokeArrow;
}
set
{
if (_strokeArrow != value)
{
_strokeArrow = value;
IsChanged = true;
}
}
}
public double FontSize = 16;
private double? _angle = 0;
public double? Angle
{
get
{
return _angle;
}
set
{
if (!Angle.HasValue || !value.HasValue ||
Angle.HasValue && value.HasValue && Math.Abs(_angle.Value - value.Value) > 11)
{
_angle = value;
IsChanged = true;
}
}
}
public bool IsChanged = true;
void ForceUpdateText()
{
_fText = new FormattedText(_text,
CultureInfo.InvariantCulture,
FlowDirection.LeftToRight,
Font,
FontSize,
Foreground);
IsChanged = true;
}
string _text;
public string Text
{
get
{
return _text;
}
set
{
if (_text != value)
{
_text = value;
ForceUpdateText();
}
}
}
Visual _child;
public virtual Visual Child
{
get
{
return _child;
}
set
{
if (_child != value)
{
if (_child != null)
{
RemoveLogicalChild(_child);
RemoveVisualChild(_child);
}
if (value != null)
{
AddVisualChild(value);
AddLogicalChild(value);
}
// cache the new child
_child = value;
InvalidateVisual();
}
}
}
public bool UpdateVisual(bool forceUpdate)
{
if (forceUpdate || IsChanged)
{
Child = Create();
IsChanged = false;
return true;
}
return false;
}
int _countCreate;
private DrawingVisual Create()
{
_countCreate++;
var square = new DrawingVisualFx();
using (var dc = square.RenderOpen())
{
dc.DrawEllipse(null,
Stroke,
new Point(Width / 2, Height / 2),
Width / 2 + Stroke.Thickness / 2,
Height / 2 + Stroke.Thickness / 2);
if (Angle.HasValue)
{
dc.PushTransform(new RotateTransform(Angle.Value, Width / 2, Height / 2));
{
var polySeg = new PolyLineSegment(new[]
{
new Point(Width * 0.2, Height * 0.3), new Point(Width * 0.8, Height * 0.3)
},
true);
var pathFig = new PathFigure(new Point(Width * 0.5, -Height * 0.22),
new PathSegment[] {polySeg},
true);
var pathGeo = new PathGeometry(new[] {pathFig});
dc.DrawGeometry(Brushes.AliceBlue, StrokeArrow, pathGeo);
}
dc.Pop();
}
dc.DrawEllipse(Background, null, new Point(Width / 2, Height / 2), Width / 2, Height / 2);
dc.DrawText(_fText, new Point(Width / 2 - _fText.Width / 2, Height / 2 - _fText.Height / 2));
}
return square;
}
#region Necessary Overrides -- Needed by WPF to maintain bookkeeping of our hosted visuals
protected override int VisualChildrenCount
{
get
{
return Child == null ? 0 : 1;
}
}
protected override Visual GetVisualChild(int index)
{
return Child;
}
#endregion
}
public class DrawingVisualFx : DrawingVisual
{
public static readonly DependencyProperty EffectProperty = DependencyProperty.Register("Effect",
typeof(Effect),
typeof(DrawingVisualFx),
new FrameworkPropertyMetadata(null,
FrameworkPropertyMetadataOptions.AffectsRender,
OnEffectChanged));
public new Effect Effect
{
get
{
return (Effect)GetValue(EffectProperty);
}
set
{
SetValue(EffectProperty, value);
}
}
private static void OnEffectChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
var drawingVisualFx = o as DrawingVisualFx;
if (drawingVisualFx != null)
{
drawingVisualFx.SetMyProtectedVisualEffect((Effect)e.NewValue);
}
}
private void SetMyProtectedVisualEffect(Effect effect)
{
VisualEffect = effect;
}
}
}
+118
View File
@@ -0,0 +1,118 @@
<UserControl x:Class="Azaion.Annotator.Controls.MapMatcher"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Azaion.Annotator.Controls"
xmlns:windowsPresentation="clr-namespace:GMap.NET.WindowsPresentation;assembly=GMap.NET.WindowsPresentation"
xmlns:controls="clr-namespace:Azaion.Common.Controls;assembly=Azaion.Common"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="1200">
<Grid
Name="MatcherGrid"
ShowGridLines="False"
Background="Black"
HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="250" /> <!-- 0 list view -->
<ColumnDefinition Width="4"/> <!-- 1 splitter -->
<ColumnDefinition Width="*" /> <!-- 2 ExplorerEditor -->
<ColumnDefinition Width="4"/> <!-- 3 splitter -->
<ColumnDefinition Width="*" /> <!-- 4 Maps Control -->
</Grid.ColumnDefinitions>
<Grid
HorizontalAlignment="Stretch"
Grid.Column="0">
<Grid.RowDefinitions>
<RowDefinition Height="28"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="30"/>
</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>
<ListView Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="2"
Name="GpsFiles"
Background="Black"
SelectedItem="{Binding Path=SelectedVideo}"
Foreground="#FFDDDDDD">
<ListView.Resources>
<Style TargetType="{x:Type ListViewItem}">
<Style.Triggers>
<DataTrigger Binding="{Binding HasAnnotations}" Value="true">
<Setter Property="Background" Value="#FF505050"/>
</DataTrigger>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Foreground" Value=" DimGray" />
<Setter Property="Background" Value="#FFCCCCCC"></Setter>
</Trigger>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Foreground" Value="DimGray"></Setter>
</Trigger>
</Style.Triggers>
<EventSetter Event="ContextMenuOpening" Handler="GpsFilesContextOpening"></EventSetter>
</Style>
</ListView.Resources>
<ListView.ContextMenu>
<ContextMenu Name="GpsFilesContextMenu">
<MenuItem Header="Відкрити папку..." Click="OpenContainingFolder" Background="WhiteSmoke" />
</ContextMenu>
</ListView.ContextMenu>
<ListView.View>
<GridView>
<GridViewColumn Width="Auto"
Header="Файл"
DisplayMemberBinding="{Binding Path=Name}"/>
</GridView>
</ListView.View>
</ListView>
</Grid>
<GridSplitter
Background="DarkGray"
ResizeDirection="Columns"
Grid.Column="1"
ResizeBehavior="PreviousAndNext"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"/>
<controls:CanvasEditor
Grid.Column="2"
x:Name="GpsImageEditor"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch" >
</controls:CanvasEditor>
<GridSplitter
Background="DarkGray"
ResizeDirection="Columns"
Grid.Column="3"
ResizeBehavior="PreviousAndNext"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"/>
<windowsPresentation:GMapControl
Grid.Column="4"
x:Name="SatelliteMap"
Zoom="20" MaxZoom="24" MinZoom="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
MinWidth="400" />
</Grid>
</UserControl>
@@ -0,0 +1,152 @@
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using Azaion.Common;
using Azaion.Common.Database;
using Azaion.Common.DTO;
using Azaion.Common.DTO.Config;
using Azaion.Common.Extensions;
using GMap.NET;
using GMap.NET.MapProviders;
using GMap.NET.WindowsPresentation;
using Microsoft.WindowsAPICodePack.Dialogs;
namespace Azaion.Annotator.Controls;
public partial class MapMatcher : UserControl
{
private AppConfig _appConfig;
List<MediaFileInfo> _allMediaFiles;
private Dictionary<string, Annotation> _annotations;
private string _currentDir;
public MapMatcher()
{
InitializeComponent();
}
public void Init(AppConfig appConfig)
{
_appConfig = appConfig;
GoogleMapProvider.Instance.ApiKey = appConfig.MapConfig.ApiKey;
SatelliteMap.MapProvider = GMapProviders.GoogleSatelliteMap;
SatelliteMap.Position = new PointLatLng(48.295985271707664, 37.14477539062501);
SatelliteMap.MultiTouchEnabled = true;
GpsFiles.MouseDoubleClick += async (sender, args) =>
{
var media = (GpsFiles.SelectedItem as MediaFileInfo)!;
var ann = _annotations.GetValueOrDefault(Path.GetFileNameWithoutExtension(media.Name));
GpsImageEditor.Background = new ImageBrush
{
ImageSource = await Path.Combine(_currentDir, ann.Name).OpenImage()
};
SatelliteMap.Position = new PointLatLng(ann.Lat, ann.Lon);
};
}
private void GpsFilesContextOpening(object sender, ContextMenuEventArgs e)
{
var listItem = sender as ListViewItem;
GpsFilesContextMenu.DataContext = listItem!.DataContext;
}
private void OpenContainingFolder(object sender, RoutedEventArgs e)
{
var mediaFileInfo = (sender as MenuItem)?.DataContext as MediaFileInfo;
if (mediaFileInfo == null)
return;
Process.Start("explorer.exe", "/select,\"" + mediaFileInfo.Path +"\"");
}
private async void OpenGpsTilesFolderClick(object sender, RoutedEventArgs e)
{
var dlg = new CommonOpenFileDialog
{
Title = "Open Video folder",
IsFolderPicker = true,
InitialDirectory = Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory)
};
var dialogResult = dlg.ShowDialog();
if (dialogResult != CommonFileDialogResult.Ok || string.IsNullOrEmpty(dlg.FileName))
return;
TbGpsMapFolder.Text = dlg.FileName;
_currentDir = dlg.FileName;
var dir = new DirectoryInfo(dlg.FileName);
var mediaFiles = dir.GetFiles(_appConfig.AnnotationConfig.ImageFormats.ToArray())
.Select(x => new MediaFileInfo
{
Name = x.Name,
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);
await Task.Delay(TimeSpan.FromSeconds(10));
SetMarkers(annotations);
}
private Dictionary<string, Annotation> SetFromCsv(List<MediaFileInfo> mediaFiles)
{
_annotations = mediaFiles.Select(x => new Annotation
{
Name = x.Name,
OriginalMediaName = x.Name
}).ToDictionary(x => Path.GetFileNameWithoutExtension(x.OriginalMediaName));
var csvResults = GpsCsvResult.ReadFromCsv(Constants.CSV_PATH);
var csvDict = csvResults.ToDictionary(x => x.Image);
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;
}
return _annotations;
}
private void SetMarkers(Dictionary<string, Annotation> annotations)
{
if (!annotations.Any())
return;
var firstAnnotation = annotations.FirstOrDefault();
SatelliteMap.Position = new PointLatLng(firstAnnotation.Value.Lat, firstAnnotation.Value.Lon);
foreach (var ann in annotations)
{
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);
}
}
}