mirror of
https://github.com/azaion/annotations.git
synced 2026-04-22 14:36:31 +00:00
add map support for gps denied
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user