mirror of
https://github.com/dotMorten/NmeaParser.git
synced 2026-01-06 16:50:20 +01:00
Add custom ArcGIS location provider to desktop sample (#69)
* Location provider sample for ArcGIS Runtime
This commit is contained in:
parent
9ab1c0cef6
commit
c9c631530e
27
src/SampleApp.WinDesktop/AltitudeGraph.xaml
Normal file
27
src/SampleApp.WinDesktop/AltitudeGraph.xaml
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
<UserControl x:Class="SampleApp.WinDesktop.AltitudeGraph"
|
||||
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"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="300" d:DesignWidth="300">
|
||||
<Grid Margin="5">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock x:Name="maxtb" VerticalAlignment="Top" FontSize="8" Margin="0,-4,0,0" />
|
||||
<TextBlock x:Name="mintb" VerticalAlignment="Bottom" FontSize="8" Margin="0,0,0,-4" />
|
||||
<Border BorderBrush="CornflowerBlue" BorderThickness="0,1"
|
||||
Grid.Column="1" Opacity=".5" />
|
||||
<Grid Grid.Column="1">
|
||||
|
||||
<Path Stretch="Fill"
|
||||
StrokeLineJoin="Round"
|
||||
Stroke="CornflowerBlue"
|
||||
StrokeThickness="2"
|
||||
x:Name="path"
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
91
src/SampleApp.WinDesktop/AltitudeGraph.xaml.cs
Normal file
91
src/SampleApp.WinDesktop/AltitudeGraph.xaml.cs
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace SampleApp.WinDesktop
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for AltitudeGraph.xaml
|
||||
/// </summary>
|
||||
public partial class AltitudeGraph : UserControl
|
||||
{
|
||||
Queue<double> datapoints = new Queue<double>();
|
||||
double min = double.MaxValue;
|
||||
double max = double.MinValue;
|
||||
public AltitudeGraph()
|
||||
{
|
||||
InitializeComponent();
|
||||
MaxDatapoints = 150;
|
||||
}
|
||||
|
||||
public void AddDataPoint(double value)
|
||||
{
|
||||
if (double.IsNaN(value))
|
||||
return;
|
||||
datapoints.Enqueue(value);
|
||||
min = Math.Min(value, double.IsNaN(min) ? value : min);
|
||||
max = Math.Max(value, double.IsNaN(max) ? value : max);
|
||||
if (datapoints.Count > MaxDatapoints)
|
||||
{
|
||||
double val = datapoints.Dequeue();
|
||||
//If this is the limiting value, recalculate min/max
|
||||
if (val == min)
|
||||
min = datapoints.Min();
|
||||
if (val == max)
|
||||
max = datapoints.Max();
|
||||
}
|
||||
UpdatePath();
|
||||
mintb.Text = min.ToString("0");
|
||||
maxtb.Text = max.ToString("0");
|
||||
}
|
||||
|
||||
private void UpdatePath()
|
||||
{
|
||||
if(!datapoints.Any())
|
||||
{
|
||||
path.Data = null;
|
||||
return;
|
||||
}
|
||||
var data = datapoints.ToArray();
|
||||
List<PathSegment> segments = new List<PathSegment>();
|
||||
for(int i=1;i<data.Length;i++)
|
||||
{
|
||||
segments.Add(new LineSegment(new Point(i, max - data[i]), true));
|
||||
}
|
||||
PathFigure pf = new PathFigure(new Point(0, max - data[0]), segments, false);
|
||||
path.Data = new PathGeometry(new PathFigure[] { pf });
|
||||
|
||||
}
|
||||
public int MaxDatapoints { get; set; }
|
||||
|
||||
|
||||
|
||||
public double Value
|
||||
{
|
||||
get { return (double)GetValue(ValueProperty); }
|
||||
set { SetValue(ValueProperty, value); }
|
||||
}
|
||||
|
||||
// Using a DependencyProperty as the backing store for Value. This enables animation, styling, binding, etc...
|
||||
public static readonly DependencyProperty ValueProperty =
|
||||
DependencyProperty.Register("Value", typeof(double), typeof(AltitudeGraph), new PropertyMetadata(0d, OnValuePropertyChanged));
|
||||
|
||||
private static void OnValuePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
((AltitudeGraph)d).AddDataPoint((double)e.NewValue);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.1"/>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1"/>
|
||||
</startup>
|
||||
</configuration>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
<Window x:Class="SampleApp.WinDesktop.MainWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:SampleApp.WinDesktop"
|
||||
xmlns:local="clr-namespace:SampleApp.WinDesktop"
|
||||
xmlns:esri="http://schemas.esri.com/arcgis/runtime/2013"
|
||||
Title="Sample App" Height="500" Width="625">
|
||||
<Window.Resources>
|
||||
<local:NullToCollapsedConverter x:Key="nullConv" />
|
||||
|
|
@ -12,89 +13,153 @@
|
|||
<Setter Property="Effect">
|
||||
<Setter.Value>
|
||||
<DropShadowEffect Direction="0" ShadowDepth="0"
|
||||
BlurRadius="20" Opacity=".5" />
|
||||
BlurRadius="20" Opacity=".5" />
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</Window.Resources>
|
||||
<Grid>
|
||||
<TabControl>
|
||||
<TabItem Header="GPS Info">
|
||||
<ScrollViewer HorizontalScrollBarVisibility="Disabled" Background="#FFEEEEEE">
|
||||
<WrapPanel x:Name="MessagePanel">
|
||||
<local:GprmcControl x:Name="gprmcView" Style="{StaticResource card}" Visibility="{Binding Message, ElementName=gprmcView, Converter={StaticResource nullConv}}" />
|
||||
<local:GpggaControl x:Name="gpggaView" Style="{StaticResource card}" Visibility="{Binding Message, ElementName=gpggaView, Converter={StaticResource nullConv}}" />
|
||||
<local:GpgsaControl x:Name="gpgsaView" Style="{StaticResource card}" Visibility="{Binding Message, ElementName=gpgsaView, Converter={StaticResource nullConv}}" />
|
||||
<local:GpgllControl x:Name="gpgllView" Style="{StaticResource card}" Visibility="{Binding Message, ElementName=gpgllView, Converter={StaticResource nullConv}}" />
|
||||
<local:PgrmeControl x:Name="pgrmeView" Style="{StaticResource card}" Visibility="{Binding Message, ElementName=pgrmeView, Converter={StaticResource nullConv}}" />
|
||||
</WrapPanel>
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
<TabItem Header="GPS Satellite view">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
<RowDefinition Height="100" />
|
||||
</Grid.RowDefinitions>
|
||||
<local:SatelliteView MaxWidth="{Binding ActualHeight, ElementName=satView}"
|
||||
Grid.Column="1" x:Name="satView" />
|
||||
<local:SatelliteSnr Grid.Row="1"
|
||||
GsvMessage="{Binding GsvMessage, ElementName=satView}" />
|
||||
</Grid>
|
||||
</TabItem>
|
||||
<TabItem Header="Messages">
|
||||
<TextBox x:Name="output"
|
||||
AcceptsReturn="True"
|
||||
IsReadOnly="True"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
HorizontalScrollBarVisibility="Visible"
|
||||
/>
|
||||
</TabItem>
|
||||
<TabItem Header="Device">
|
||||
<Grid>
|
||||
<StackPanel Margin="10">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="Current device: " />
|
||||
<TextBlock Text="None" x:Name="currentDeviceInfo" FontWeight="Bold" />
|
||||
</StackPanel>
|
||||
<Button Width="200" Content="Open NMEA Log..." Click="OpenNmeaLogButton_Click" HorizontalAlignment="Left" Padding="20,5" Margin="0,5" />
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Button Width="200" Content="Auto-discover serial port"
|
||||
HorizontalAlignment="Left" Padding="20,5" Margin="0,5"
|
||||
Click="AutoDiscoverButton_Click" />
|
||||
<TextBlock x:Name="autoDiscoverStatus" VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
<GroupBox Header="Open Serial device" Width="200" HorizontalAlignment="Left">
|
||||
<StackPanel>
|
||||
<TextBlock Text="Serial port:" />
|
||||
<ComboBox x:Name="serialPorts" />
|
||||
<TextBlock Text="Baud rate:" />
|
||||
<ComboBox x:Name="baudRates" SelectedIndex="3">
|
||||
<ComboBoxItem>1200</ComboBoxItem>
|
||||
<ComboBoxItem>2400</ComboBoxItem>
|
||||
<ComboBoxItem>4800</ComboBoxItem>
|
||||
<ComboBoxItem>9600</ComboBoxItem>
|
||||
<ComboBoxItem>19200</ComboBoxItem>
|
||||
<ComboBoxItem>38400</ComboBoxItem>
|
||||
<ComboBoxItem>57600</ComboBoxItem>
|
||||
<ComboBoxItem>115200</ComboBoxItem>
|
||||
</ComboBox>
|
||||
<Button Content="Connect" HorizontalAlignment="Left" Padding="20,5" Margin="0,5" Click="ConnectToSerialButton_Click" />
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</Grid>
|
||||
<TabControl>
|
||||
<TabItem Header="GPS Info">
|
||||
<ScrollViewer HorizontalScrollBarVisibility="Disabled" Background="#FFEEEEEE">
|
||||
<WrapPanel x:Name="MessagePanel">
|
||||
<local:GprmcControl x:Name="gprmcView" Style="{StaticResource card}" Visibility="{Binding Message, ElementName=gprmcView, Converter={StaticResource nullConv}}" />
|
||||
<local:GpggaControl x:Name="gpggaView" Style="{StaticResource card}" Visibility="{Binding Message, ElementName=gpggaView, Converter={StaticResource nullConv}}" />
|
||||
<local:GpgsaControl x:Name="gpgsaView" Style="{StaticResource card}" Visibility="{Binding Message, ElementName=gpgsaView, Converter={StaticResource nullConv}}" />
|
||||
<local:GpgllControl x:Name="gpgllView" Style="{StaticResource card}" Visibility="{Binding Message, ElementName=gpgllView, Converter={StaticResource nullConv}}" />
|
||||
<local:PgrmeControl x:Name="pgrmeView" Style="{StaticResource card}" Visibility="{Binding Message, ElementName=pgrmeView, Converter={StaticResource nullConv}}" />
|
||||
</WrapPanel>
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
<TabItem Header="GPS Satellite view">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
<RowDefinition Height="100" />
|
||||
</Grid.RowDefinitions>
|
||||
<local:SatelliteView MaxWidth="{Binding ActualHeight, ElementName=satView}"
|
||||
Grid.Column="1" x:Name="satView" />
|
||||
<local:SatelliteSnr Grid.Row="1"
|
||||
GsvMessage="{Binding GsvMessage, ElementName=satView}" />
|
||||
</Grid>
|
||||
</TabItem>
|
||||
<TabItem Header="Map">
|
||||
<Grid>
|
||||
<!--Map-->
|
||||
<esri:MapView x:Name="mapView" Grid.Row="1">
|
||||
<local:RestoreAutoPanMode.RestoreAutoPanSettings>
|
||||
<local:RestoreAutoPanMode DelayInSeconds="2.5" PanMode="Navigation" IsEnabled="True" RestoreScale="5000" />
|
||||
</local:RestoreAutoPanMode.RestoreAutoPanSettings>
|
||||
</esri:MapView>
|
||||
|
||||
<!--North arrow-->
|
||||
<Grid Width="50" Height="50" HorizontalAlignment="Right" VerticalAlignment="Bottom"
|
||||
Margin="20" RenderTransformOrigin=".5,.5">
|
||||
<Grid.Resources>
|
||||
<local:ReverseConverter x:Key="conv" />
|
||||
</Grid.Resources>
|
||||
<Grid.Effect>
|
||||
<DropShadowEffect Direction="0" ShadowDepth="0"
|
||||
BlurRadius="10" Opacity=".75" />
|
||||
</Grid.Effect>
|
||||
<Grid.RenderTransform>
|
||||
<RotateTransform Angle="{Binding ElementName=mapView, Path=MapRotation, Converter={StaticResource conv}}" />
|
||||
</Grid.RenderTransform>
|
||||
<TextBlock Text="Ù" FontFamily="Wingdings" HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
FontSize="40" Foreground="White" />
|
||||
<TextBlock Text="N" HorizontalAlignment="Center" VerticalAlignment="Top"
|
||||
FontSize="12" Margin="0,-5,0,0" RenderTransformOrigin=".5,.5"
|
||||
Foreground="White" >
|
||||
<TextBlock.RenderTransform>
|
||||
<RotateTransform Angle="{Binding ElementName=mapView, Path=MapRotation}" />
|
||||
</TextBlock.RenderTransform>
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
|
||||
<Grid HorizontalAlignment="Right" VerticalAlignment="Top"
|
||||
Margin="10" Background="White" Width="150"
|
||||
DataContext="{Binding ElementName=mapView, Path=LocationDisplay.Location}">
|
||||
<Grid.Effect>
|
||||
<DropShadowEffect Direction="0" ShadowDepth="0"
|
||||
BlurRadius="10" Opacity=".75" />
|
||||
</Grid.Effect>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
<Border Background="CornflowerBlue" Grid.ColumnSpan="2" Padding="10">
|
||||
<TextBlock Text="Details" FontSize="20" FontWeight="Bold" Foreground="White" />
|
||||
</Border>
|
||||
<StackPanel Margin="10" Grid.Row="1">
|
||||
<TextBlock Text="Speed:" FontWeight="Bold" />
|
||||
<TextBlock Text="Course:" FontWeight="Bold" />
|
||||
<TextBlock Text="Altitude:" FontWeight="Bold" />
|
||||
<TextBlock Text="Accuracy:" FontWeight="Bold" />
|
||||
</StackPanel>
|
||||
<StackPanel Margin="0,10,10,10" Grid.Column="1" Grid.Row="1">
|
||||
<TextBlock Text="{Binding Velocity, StringFormat='{}{0} km/h'}" />
|
||||
<TextBlock Text="{Binding Course, StringFormat='{}{0}°'}" />
|
||||
<TextBlock Text="{Binding Position.Z, StringFormat='{}{0} m'}" />
|
||||
<TextBlock Text="{Binding HorizontalAccuracy, StringFormat='{}{0} m'}" />
|
||||
</StackPanel>
|
||||
<local:AltitudeGraph Grid.Row="2" Grid.ColumnSpan="2" Height="50" x:Name="altitude" Value="{Binding Position.Z}" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</TabItem>
|
||||
<TabItem Header="Messages">
|
||||
<TextBox x:Name="output"
|
||||
AcceptsReturn="True"
|
||||
IsReadOnly="True"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
HorizontalScrollBarVisibility="Visible"
|
||||
/>
|
||||
</TabItem>
|
||||
<TabItem Header="Device">
|
||||
<Grid>
|
||||
<StackPanel Margin="10">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="Current device: " />
|
||||
<TextBlock Text="None" x:Name="currentDeviceInfo" FontWeight="Bold" />
|
||||
</StackPanel>
|
||||
<Button Width="200" Content="Open NMEA Log..." Click="OpenNmeaLogButton_Click" HorizontalAlignment="Left" Padding="20,5" Margin="0,5" />
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Button Width="200" Content="Auto-discover serial port"
|
||||
HorizontalAlignment="Left" Padding="20,5" Margin="0,5"
|
||||
Click="AutoDiscoverButton_Click" />
|
||||
<TextBlock x:Name="autoDiscoverStatus" VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
<GroupBox Header="Open Serial device" Width="200" HorizontalAlignment="Left">
|
||||
<StackPanel>
|
||||
<TextBlock Text="Serial port:" />
|
||||
<ComboBox x:Name="serialPorts" />
|
||||
<TextBlock Text="Baud rate:" />
|
||||
<ComboBox x:Name="baudRates" SelectedIndex="3">
|
||||
<ComboBoxItem>1200</ComboBoxItem>
|
||||
<ComboBoxItem>2400</ComboBoxItem>
|
||||
<ComboBoxItem>4800</ComboBoxItem>
|
||||
<ComboBoxItem>9600</ComboBoxItem>
|
||||
<ComboBoxItem>19200</ComboBoxItem>
|
||||
<ComboBoxItem>38400</ComboBoxItem>
|
||||
<ComboBoxItem>57600</ComboBoxItem>
|
||||
<ComboBoxItem>115200</ComboBoxItem>
|
||||
</ComboBox>
|
||||
<Button Content="Connect" HorizontalAlignment="Left" Padding="20,5" Margin="0,5" Click="ConnectToSerialButton_Click" />
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
</Grid>
|
||||
</Window>
|
||||
|
|
|
|||
|
|
@ -1,230 +1,248 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
using NmeaParser;
|
||||
|
||||
namespace SampleApp.WinDesktop
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for MainWindow.xaml
|
||||
/// </summary>
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
private Queue<string> messages = new Queue<string>(101);
|
||||
private NmeaParser.NmeaDevice currentDevice;
|
||||
//Dialog for browsing to nmea log files
|
||||
private Microsoft.Win32.OpenFileDialog nmeaOpenFileDialog = new Microsoft.Win32.OpenFileDialog()
|
||||
{
|
||||
Filter = "Text files|*.txt|NMEA Log|*.nmea|All files|*.*",
|
||||
InitialDirectory = new System.IO.FileInfo(typeof(MainWindow).Assembly.Location).DirectoryName
|
||||
};
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
//Get list of serial ports for device tab
|
||||
var availableSerialPorts = System.IO.Ports.SerialPort.GetPortNames().OrderBy(s=>s);
|
||||
serialPorts.ItemsSource = availableSerialPorts;
|
||||
serialPorts.SelectedIndex = 0;
|
||||
// Use serial portName:
|
||||
//var comPort = availableSerialPorts.First();
|
||||
//var portName = new System.IO.Ports.SerialPort(comPort, 4800);
|
||||
//var device = new NmeaParser.SerialPortDevice(portName);
|
||||
|
||||
//Use a log file for playing back logged data
|
||||
var device = new NmeaParser.NmeaFileDevice("NmeaSampleData.txt");
|
||||
|
||||
StartDevice(device);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unloads the current device, and opens the next device
|
||||
/// </summary>
|
||||
/// <param name="device"></param>
|
||||
private async void StartDevice(NmeaParser.NmeaDevice device)
|
||||
{
|
||||
//Clean up old device
|
||||
if (currentDevice != null)
|
||||
{
|
||||
currentDevice.MessageReceived -= device_MessageReceived;
|
||||
if (currentDevice.IsOpen)
|
||||
await currentDevice.CloseAsync();
|
||||
currentDevice.Dispose();
|
||||
}
|
||||
output.Text = "";
|
||||
messages.Clear();
|
||||
gprmcView.Message = null;
|
||||
gpggaView.Message = null;
|
||||
gpgsaView.Message = null;
|
||||
gpgllView.Message = null;
|
||||
pgrmeView.Message = null;
|
||||
satView.GsvMessage = null;
|
||||
//Start new device
|
||||
currentDevice = device;
|
||||
currentDevice.MessageReceived += device_MessageReceived;
|
||||
var _ = currentDevice.OpenAsync();
|
||||
if (device is NmeaParser.NmeaFileDevice)
|
||||
currentDeviceInfo.Text = string.Format("NmeaFileDevice( file={0} )", ((NmeaParser.NmeaFileDevice)device).FileName);
|
||||
else if (device is NmeaParser.SerialPortDevice)
|
||||
{
|
||||
currentDeviceInfo.Text = string.Format("SerialPortDevice( port={0}, baud={1} )",
|
||||
((NmeaParser.SerialPortDevice)device).Port.PortName,
|
||||
((NmeaParser.SerialPortDevice)device).Port.BaudRate);
|
||||
}
|
||||
}
|
||||
|
||||
private void device_MessageReceived(object sender, NmeaParser.NmeaMessageReceivedEventArgs args)
|
||||
{
|
||||
Dispatcher.BeginInvoke((Action) delegate()
|
||||
{
|
||||
messages.Enqueue(args.Message.ToString());
|
||||
if (messages.Count > 100) messages.Dequeue(); //Keep message queue at 100
|
||||
output.Text = string.Join("\n", messages.ToArray());
|
||||
output.Select(output.Text.Length - 1, 0); //scroll to bottom
|
||||
|
||||
if (args.Message is NmeaParser.Messages.Gsv gpgsv)
|
||||
{
|
||||
satView.GsvMessage = gpgsv;
|
||||
}
|
||||
else if (args.Message is NmeaParser.Messages.Rmc)
|
||||
gprmcView.Message = args.Message as NmeaParser.Messages.Rmc;
|
||||
else if (args.Message is NmeaParser.Messages.Gga)
|
||||
gpggaView.Message = args.Message as NmeaParser.Messages.Gga;
|
||||
else if (args.Message is NmeaParser.Messages.Gsa)
|
||||
gpgsaView.Message = args.Message as NmeaParser.Messages.Gsa;
|
||||
else if (args.Message is NmeaParser.Messages.Gll)
|
||||
gpgllView.Message = args.Message as NmeaParser.Messages.Gll;
|
||||
else if (args.Message is NmeaParser.Messages.Garmin.Pgrme)
|
||||
pgrmeView.Message = args.Message as NmeaParser.Messages.Garmin.Pgrme;
|
||||
else
|
||||
{
|
||||
var ctrl = MessagePanel.Children.OfType<UnknownMessageControl>().Where(c => c.Message.MessageType == args.Message.MessageType).FirstOrDefault();
|
||||
if (ctrl == null)
|
||||
{
|
||||
ctrl = new UnknownMessageControl()
|
||||
{
|
||||
Style = this.Resources["card"] as Style
|
||||
};
|
||||
MessagePanel.Children.Add(ctrl);
|
||||
}
|
||||
ctrl.Message = args.Message;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//Browse to nmea file and create device from selected file
|
||||
private void OpenNmeaLogButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var result = nmeaOpenFileDialog.ShowDialog();
|
||||
if (result.HasValue && result.Value)
|
||||
{
|
||||
var file = nmeaOpenFileDialog.FileName;
|
||||
var device = new NmeaParser.NmeaFileDevice(file);
|
||||
StartDevice(device);
|
||||
}
|
||||
}
|
||||
|
||||
//Creates a serial port device from the selected settings
|
||||
private void ConnectToSerialButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
var portName = serialPorts.Text as string;
|
||||
var baudRate = int.Parse(baudRates.Text);
|
||||
var device = new NmeaParser.SerialPortDevice(new System.IO.Ports.SerialPort(portName, baudRate));
|
||||
StartDevice(device);
|
||||
}
|
||||
catch(System.Exception ex)
|
||||
{
|
||||
MessageBox.Show("Error connecting: " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
//Attempts to perform an auto discovery of serial ports
|
||||
private async void AutoDiscoverButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var button = sender as Button;
|
||||
button.IsEnabled = false;
|
||||
System.IO.Ports.SerialPort port = await Task.Run<System.IO.Ports.SerialPort>(() => {
|
||||
return FindPort(
|
||||
new System.Progress<string>((s) => { Dispatcher.BeginInvoke((Action)delegate() { autoDiscoverStatus.Text = s; }); }));
|
||||
});
|
||||
if (port != null) //we found a port
|
||||
{
|
||||
autoDiscoverStatus.Text = "";
|
||||
serialPorts.Text = port.PortName;
|
||||
baudRates.Text = port.BaudRate.ToString();
|
||||
ConnectToSerialButton_Click(sender, e);
|
||||
}
|
||||
else
|
||||
autoDiscoverStatus.Text = "No GPS port found";
|
||||
button.IsEnabled = false;
|
||||
}
|
||||
|
||||
//Iterates all serial ports and attempts to open them at different baud rates
|
||||
//and looks for a GPS message.
|
||||
private static System.IO.Ports.SerialPort FindPort(IProgress<string> progress = null)
|
||||
{
|
||||
var ports = System.IO.Ports.SerialPort.GetPortNames().OrderBy(s => s);
|
||||
foreach (var portName in ports)
|
||||
{
|
||||
using (var port = new System.IO.Ports.SerialPort(portName))
|
||||
{
|
||||
var defaultRate = port.BaudRate;
|
||||
List<int> baudRatesToTest = new List<int>(new[] { 9600, 4800, 115200, 19200, 57600, 38400, 2400 }); //Ordered by likelihood
|
||||
//Move default rate to first spot
|
||||
if (baudRatesToTest.Contains(defaultRate)) baudRatesToTest.Remove(defaultRate);
|
||||
baudRatesToTest.Insert(0, defaultRate);
|
||||
foreach (var baud in baudRatesToTest)
|
||||
{
|
||||
|
||||
if (progress != null)
|
||||
progress.Report(string.Format("Trying {0} @ {1}baud", portName, port.BaudRate));
|
||||
port.BaudRate = baud;
|
||||
port.ReadTimeout = 2000; //this might not be long enough
|
||||
bool success = false;
|
||||
try
|
||||
{
|
||||
port.Open();
|
||||
if (!port.IsOpen)
|
||||
continue; //couldn't open port
|
||||
try
|
||||
{
|
||||
port.ReadTo("$GP");
|
||||
}
|
||||
catch (TimeoutException)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
success = true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
//Error reading
|
||||
}
|
||||
finally
|
||||
{
|
||||
port.Close();
|
||||
}
|
||||
if (success)
|
||||
{
|
||||
return new System.IO.Ports.SerialPort(portName, baud);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
using Esri.ArcGISRuntime.Mapping;
|
||||
using NmeaParser;
|
||||
|
||||
namespace SampleApp.WinDesktop
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for MainWindow.xaml
|
||||
/// </summary>
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
private Queue<string> messages = new Queue<string>(101);
|
||||
private NmeaParser.NmeaDevice currentDevice;
|
||||
//Dialog for browsing to nmea log files
|
||||
private Microsoft.Win32.OpenFileDialog nmeaOpenFileDialog = new Microsoft.Win32.OpenFileDialog()
|
||||
{
|
||||
Filter = "Text files|*.txt|NMEA Log|*.nmea|All files|*.*",
|
||||
InitialDirectory = new System.IO.FileInfo(typeof(MainWindow).Assembly.Location).DirectoryName
|
||||
};
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
mapView.Map = new Map(Basemap.CreateNavigationVector());
|
||||
|
||||
//Get list of serial ports for device tab
|
||||
var availableSerialPorts = System.IO.Ports.SerialPort.GetPortNames().OrderBy(s=>s);
|
||||
serialPorts.ItemsSource = availableSerialPorts;
|
||||
serialPorts.SelectedIndex = 0;
|
||||
// Use serial portName:
|
||||
//var comPort = availableSerialPorts.First();
|
||||
//var portName = new System.IO.Ports.SerialPort(comPort, 4800);
|
||||
//var device = new NmeaParser.SerialPortDevice(portName);
|
||||
|
||||
//Use a log file for playing back logged data
|
||||
var device = new NmeaParser.NmeaFileDevice("NmeaSampleData.txt");
|
||||
|
||||
StartDevice(device);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unloads the current device, and opens the next device
|
||||
/// </summary>
|
||||
/// <param name="device"></param>
|
||||
private async void StartDevice(NmeaParser.NmeaDevice device)
|
||||
{
|
||||
//Clean up old device
|
||||
if (currentDevice != null)
|
||||
{
|
||||
currentDevice.MessageReceived -= device_MessageReceived;
|
||||
if (currentDevice.IsOpen)
|
||||
await currentDevice.CloseAsync();
|
||||
currentDevice.Dispose();
|
||||
}
|
||||
output.Text = "";
|
||||
messages.Clear();
|
||||
gprmcView.Message = null;
|
||||
gpggaView.Message = null;
|
||||
gpgsaView.Message = null;
|
||||
gpgllView.Message = null;
|
||||
pgrmeView.Message = null;
|
||||
satView.GsvMessage = null;
|
||||
//Start new device
|
||||
currentDevice = device;
|
||||
currentDevice.MessageReceived += device_MessageReceived;
|
||||
mapView.LocationDisplay.DataSource = new NmeaLocationProvider(device);
|
||||
mapView.LocationDisplay.IsEnabled = true;
|
||||
mapView.LocationDisplay.InitialZoomScale = 5000;
|
||||
mapView.LocationDisplay.AutoPanMode = Esri.ArcGISRuntime.UI.LocationDisplayAutoPanMode.Navigation;
|
||||
if (device is NmeaParser.NmeaFileDevice)
|
||||
currentDeviceInfo.Text = string.Format("NmeaFileDevice( file={0} )", ((NmeaParser.NmeaFileDevice)device).FileName);
|
||||
else if (device is NmeaParser.SerialPortDevice)
|
||||
{
|
||||
currentDeviceInfo.Text = string.Format("SerialPortDevice( port={0}, baud={1} )",
|
||||
((NmeaParser.SerialPortDevice)device).Port.PortName,
|
||||
((NmeaParser.SerialPortDevice)device).Port.BaudRate);
|
||||
}
|
||||
}
|
||||
|
||||
private void device_MessageReceived(object sender, NmeaParser.NmeaMessageReceivedEventArgs args)
|
||||
{
|
||||
Dispatcher.BeginInvoke((Action) delegate()
|
||||
{
|
||||
messages.Enqueue(args.Message.ToString());
|
||||
if (messages.Count > 100) messages.Dequeue(); //Keep message queue at 100
|
||||
output.Text = string.Join("\n", messages.ToArray());
|
||||
output.Select(output.Text.Length - 1, 0); //scroll to bottom
|
||||
|
||||
if (args.Message is NmeaParser.Messages.Gsv gpgsv)
|
||||
{
|
||||
satView.GsvMessage = gpgsv;
|
||||
}
|
||||
else if (args.Message is NmeaParser.Messages.Rmc)
|
||||
gprmcView.Message = args.Message as NmeaParser.Messages.Rmc;
|
||||
else if (args.Message is NmeaParser.Messages.Gga)
|
||||
gpggaView.Message = args.Message as NmeaParser.Messages.Gga;
|
||||
else if (args.Message is NmeaParser.Messages.Gsa)
|
||||
gpgsaView.Message = args.Message as NmeaParser.Messages.Gsa;
|
||||
else if (args.Message is NmeaParser.Messages.Gll)
|
||||
gpgllView.Message = args.Message as NmeaParser.Messages.Gll;
|
||||
else if (args.Message is NmeaParser.Messages.Garmin.Pgrme)
|
||||
pgrmeView.Message = args.Message as NmeaParser.Messages.Garmin.Pgrme;
|
||||
else
|
||||
{
|
||||
var ctrl = MessagePanel.Children.OfType<UnknownMessageControl>().Where(c => c.Message.MessageType == args.Message.MessageType).FirstOrDefault();
|
||||
if (ctrl == null)
|
||||
{
|
||||
ctrl = new UnknownMessageControl()
|
||||
{
|
||||
Style = this.Resources["card"] as Style
|
||||
};
|
||||
MessagePanel.Children.Add(ctrl);
|
||||
}
|
||||
ctrl.Message = args.Message;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//Browse to nmea file and create device from selected file
|
||||
private void OpenNmeaLogButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var result = nmeaOpenFileDialog.ShowDialog();
|
||||
if (result.HasValue && result.Value)
|
||||
{
|
||||
var file = nmeaOpenFileDialog.FileName;
|
||||
var device = new NmeaParser.NmeaFileDevice(file);
|
||||
StartDevice(device);
|
||||
}
|
||||
}
|
||||
|
||||
//Creates a serial port device from the selected settings
|
||||
private void ConnectToSerialButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
var portName = serialPorts.Text as string;
|
||||
var baudRate = int.Parse(baudRates.Text);
|
||||
var device = new NmeaParser.SerialPortDevice(new System.IO.Ports.SerialPort(portName, baudRate));
|
||||
StartDevice(device);
|
||||
}
|
||||
catch(System.Exception ex)
|
||||
{
|
||||
MessageBox.Show("Error connecting: " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
//Attempts to perform an auto discovery of serial ports
|
||||
private async void AutoDiscoverButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var button = sender as Button;
|
||||
button.IsEnabled = false;
|
||||
System.IO.Ports.SerialPort port = await Task.Run<System.IO.Ports.SerialPort>(() => {
|
||||
return FindPort(
|
||||
new System.Progress<string>((s) => { Dispatcher.BeginInvoke((Action)delegate() { autoDiscoverStatus.Text = s; }); }));
|
||||
});
|
||||
if (port != null) //we found a port
|
||||
{
|
||||
autoDiscoverStatus.Text = "";
|
||||
serialPorts.Text = port.PortName;
|
||||
baudRates.Text = port.BaudRate.ToString();
|
||||
ConnectToSerialButton_Click(sender, e);
|
||||
}
|
||||
else
|
||||
autoDiscoverStatus.Text = "No GPS port found";
|
||||
button.IsEnabled = false;
|
||||
}
|
||||
|
||||
//Iterates all serial ports and attempts to open them at different baud rates
|
||||
//and looks for a GPS message.
|
||||
private static System.IO.Ports.SerialPort FindPort(IProgress<string> progress = null)
|
||||
{
|
||||
var ports = System.IO.Ports.SerialPort.GetPortNames().OrderBy(s => s);
|
||||
foreach (var portName in ports)
|
||||
{
|
||||
using (var port = new System.IO.Ports.SerialPort(portName))
|
||||
{
|
||||
var defaultRate = port.BaudRate;
|
||||
List<int> baudRatesToTest = new List<int>(new[] { 9600, 4800, 115200, 19200, 57600, 38400, 2400 }); //Ordered by likelihood
|
||||
//Move default rate to first spot
|
||||
if (baudRatesToTest.Contains(defaultRate)) baudRatesToTest.Remove(defaultRate);
|
||||
baudRatesToTest.Insert(0, defaultRate);
|
||||
foreach (var baud in baudRatesToTest)
|
||||
{
|
||||
|
||||
if (progress != null)
|
||||
progress.Report(string.Format("Trying {0} @ {1}baud", portName, port.BaudRate));
|
||||
port.BaudRate = baud;
|
||||
port.ReadTimeout = 2000; //this might not be long enough
|
||||
bool success = false;
|
||||
try
|
||||
{
|
||||
port.Open();
|
||||
if (!port.IsOpen)
|
||||
continue; //couldn't open port
|
||||
try
|
||||
{
|
||||
port.ReadTo("$GP");
|
||||
}
|
||||
catch (TimeoutException)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
success = true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
//Error reading
|
||||
}
|
||||
finally
|
||||
{
|
||||
port.Close();
|
||||
}
|
||||
if (success)
|
||||
{
|
||||
return new System.IO.Ports.SerialPort(portName, baud);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public class ReverseConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
|
||||
{
|
||||
return -(double)value;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
|
||||
{
|
||||
return -(double)value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
100
src/SampleApp.WinDesktop/NmeaProvider.cs
Normal file
100
src/SampleApp.WinDesktop/NmeaProvider.cs
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
using Esri.ArcGISRuntime.Geometry;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SampleApp.WinDesktop
|
||||
{
|
||||
public class NmeaLocationProvider : Esri.ArcGISRuntime.Location.LocationDataSource
|
||||
{
|
||||
private NmeaParser.NmeaDevice device;
|
||||
double m_Accuracy = 0;
|
||||
double m_altitude = double.NaN;
|
||||
double m_speed = 0;
|
||||
double m_course = 0;
|
||||
|
||||
public NmeaLocationProvider(NmeaParser.NmeaDevice device)
|
||||
{
|
||||
this.device = device;
|
||||
if(device != null)
|
||||
device.MessageReceived += device_MessageReceived;
|
||||
}
|
||||
void device_MessageReceived(object sender, NmeaParser.NmeaMessageReceivedEventArgs e)
|
||||
{
|
||||
var message = e.Message;
|
||||
ParseMessage(message);
|
||||
}
|
||||
|
||||
public void ParseMessage(NmeaParser.Messages.NmeaMessage message)
|
||||
{
|
||||
bool isNewFix = false;
|
||||
bool lostFix = false;
|
||||
double lat = 0;
|
||||
double lon = 0;
|
||||
if (message is NmeaParser.Messages.Garmin.Pgrme)
|
||||
{
|
||||
m_Accuracy = ((NmeaParser.Messages.Garmin.Pgrme)message).HorizontalError;
|
||||
}
|
||||
else if(message is NmeaParser.Messages.Gst)
|
||||
{
|
||||
Gst = ((NmeaParser.Messages.Gst)message);
|
||||
m_Accuracy = Math.Sqrt(Gst.SigmaLatitudeError * Gst.SigmaLatitudeError + Gst.SigmaLongitudeError * Gst.SigmaLongitudeError);
|
||||
}
|
||||
else if(message is NmeaParser.Messages.Gga)
|
||||
{
|
||||
Gga = ((NmeaParser.Messages.Gga)message);
|
||||
isNewFix = Gga.Quality != NmeaParser.Messages.Gga.FixQuality.Invalid;
|
||||
lostFix = !isNewFix;
|
||||
m_altitude = Gga.Altitude;
|
||||
lat = Gga.Latitude;
|
||||
lon = Gga.Longitude;
|
||||
}
|
||||
else if (message is NmeaParser.Messages.Rmc)
|
||||
{
|
||||
Rmc = (NmeaParser.Messages.Rmc)message;
|
||||
if (Rmc.Active)
|
||||
{
|
||||
isNewFix = true;
|
||||
m_speed = Rmc.Speed;
|
||||
m_course = Rmc.Course;
|
||||
lat = Rmc.Latitude;
|
||||
lon = Rmc.Longitude;
|
||||
}
|
||||
else lostFix = true;
|
||||
}
|
||||
else if (message is NmeaParser.Messages.Gsa)
|
||||
{
|
||||
Gsa = (NmeaParser.Messages.Gsa)message;
|
||||
}
|
||||
if (isNewFix)
|
||||
{
|
||||
base.UpdateLocation(new Esri.ArcGISRuntime.Location.Location(new MapPoint(lon, lat, m_altitude, SpatialReferences.Wgs84), m_Accuracy, m_speed, m_course, false));
|
||||
}
|
||||
else if (lostFix)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
protected override Task OnStartAsync()
|
||||
{
|
||||
if (device != null)
|
||||
return this.device.OpenAsync();
|
||||
else
|
||||
return System.Threading.Tasks.Task<bool>.FromResult(true);
|
||||
}
|
||||
|
||||
protected override Task OnStopAsync()
|
||||
{
|
||||
m_Accuracy = double.NaN;
|
||||
if(this.device != null)
|
||||
return this.device.CloseAsync();
|
||||
else
|
||||
return System.Threading.Tasks.Task<bool>.FromResult(true);
|
||||
}
|
||||
|
||||
public NmeaParser.Messages.Gsa Gsa { get; private set; }
|
||||
public NmeaParser.Messages.Gga Gga { get; private set; }
|
||||
public NmeaParser.Messages.Rmc Rmc { get; private set; }
|
||||
public NmeaParser.Messages.Gst Gst { get; private set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,63 +0,0 @@
|
|||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace SampleApp.WinDesktop.Properties {
|
||||
using System;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||
/// </summary>
|
||||
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
internal class Resources {
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal Resources() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cached ResourceManager instance used by this class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Resources.ResourceManager ResourceManager {
|
||||
get {
|
||||
if (object.ReferenceEquals(resourceMan, null)) {
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("SampleApp.WinDesktop.Properties.Resources", typeof(Resources).Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the current thread's CurrentUICulture property for all
|
||||
/// resource lookups using this strongly typed resource class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Globalization.CultureInfo Culture {
|
||||
get {
|
||||
return resourceCulture;
|
||||
}
|
||||
set {
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,117 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
</root>
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace SampleApp.WinDesktop.Properties {
|
||||
|
||||
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.5.0.0")]
|
||||
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
|
||||
|
||||
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
|
||||
|
||||
public static Settings Default {
|
||||
get {
|
||||
return defaultInstance;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<SettingsFile xmlns="uri:settings" CurrentProfile="(Default)">
|
||||
<Profiles>
|
||||
<Profile Name="(Default)" />
|
||||
</Profiles>
|
||||
<Settings />
|
||||
</SettingsFile>
|
||||
148
src/SampleApp.WinDesktop/RestoreAutoPanMode.cs
Normal file
148
src/SampleApp.WinDesktop/RestoreAutoPanMode.cs
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
using Esri.ArcGISRuntime.UI.Controls;
|
||||
using Esri.ArcGISRuntime.Location;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
using System.ComponentModel;
|
||||
#if NETFX_CORE
|
||||
using Windows.UI.Xaml;
|
||||
#else
|
||||
using System.Windows.Threading;
|
||||
#endif
|
||||
|
||||
|
||||
namespace SampleApp.WinDesktop
|
||||
{
|
||||
public class RestoreAutoPanMode
|
||||
{
|
||||
private class DelayTimer
|
||||
{
|
||||
private Action m_action;
|
||||
DispatcherTimer m_timer;
|
||||
public DelayTimer(Action action)
|
||||
{
|
||||
m_timer = new DispatcherTimer();
|
||||
m_timer.Tick += m_timer_Tick;
|
||||
m_action = action;
|
||||
}
|
||||
|
||||
|
||||
#if NETFX_CORE
|
||||
void m_timer_Tick(object sender, object e)
|
||||
#else
|
||||
void m_timer_Tick(object sender, EventArgs e)
|
||||
#endif
|
||||
{
|
||||
m_timer.Stop();
|
||||
if (m_action != null)
|
||||
m_action();
|
||||
}
|
||||
public void Invoke(TimeSpan delay)
|
||||
{
|
||||
m_timer.Stop();
|
||||
m_timer.Interval = delay;
|
||||
m_timer.Start();
|
||||
}
|
||||
public void Cancel()
|
||||
{
|
||||
m_timer.Stop();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private MapView m_mapView;
|
||||
private DelayTimer m_timer;
|
||||
|
||||
|
||||
public RestoreAutoPanMode()
|
||||
{
|
||||
m_timer = new DelayTimer(ResetPanMode);
|
||||
RestoreScale = double.NaN;
|
||||
}
|
||||
|
||||
|
||||
private void ResetPanMode()
|
||||
{
|
||||
if (m_mapView != null && m_mapView.LocationDisplay != null)
|
||||
{
|
||||
if (!double.IsNaN(RestoreScale))
|
||||
m_mapView.SetViewpointScaleAsync(RestoreScale);
|
||||
m_mapView.LocationDisplay.AutoPanMode = this.PanMode;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal void AttachToMapView(MapView mv)
|
||||
{
|
||||
if (m_mapView != null && m_mapView != mv)
|
||||
throw new InvalidOperationException("RestoreAutoPanMode can only be assigned to one mapview");
|
||||
m_mapView = mv;
|
||||
(m_mapView as INotifyPropertyChanged).PropertyChanged += m_mapView_PropertyChanged;
|
||||
}
|
||||
|
||||
internal void DetachFromMapView(MapView mv)
|
||||
{
|
||||
(m_mapView as INotifyPropertyChanged).PropertyChanged -= m_mapView_PropertyChanged;
|
||||
m_mapView = null;
|
||||
}
|
||||
|
||||
private void m_mapView_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||
{
|
||||
//If user stopped navigating and we're not in the correct autopan mode,
|
||||
//restore autopan after the set delay.
|
||||
if (IsEnabled && e.PropertyName == "IsNavigating")
|
||||
{
|
||||
if (m_mapView.LocationDisplay != null &&
|
||||
m_mapView.LocationDisplay.AutoPanMode != PanMode)
|
||||
{
|
||||
if (!m_mapView.IsNavigating)
|
||||
m_timer.Invoke(TimeSpan.FromSeconds(DelayInSeconds));
|
||||
else
|
||||
m_timer.Cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsEnabled { get; set; }
|
||||
|
||||
public double DelayInSeconds { get; set; }
|
||||
|
||||
public Esri.ArcGISRuntime.UI.LocationDisplayAutoPanMode PanMode { get; set; }
|
||||
|
||||
public double RestoreScale { get; set; }
|
||||
|
||||
|
||||
public static RestoreAutoPanMode GetRestoreAutoPanSettings(DependencyObject obj)
|
||||
{
|
||||
return (RestoreAutoPanMode)obj.GetValue(RestoreAutoPanSettingsProperty);
|
||||
}
|
||||
|
||||
|
||||
public static void SetRestoreAutoPanSettings(DependencyObject obj, RestoreAutoPanMode value)
|
||||
{
|
||||
obj.SetValue(RestoreAutoPanSettingsProperty, value);
|
||||
}
|
||||
|
||||
|
||||
public static readonly DependencyProperty RestoreAutoPanSettingsProperty =
|
||||
DependencyProperty.RegisterAttached("RestoreAutoPanSettings", typeof(RestoreAutoPanMode), typeof(RestoreAutoPanMode),
|
||||
new PropertyMetadata(null, OnRestoreAutoPanSettingsChanged));
|
||||
|
||||
|
||||
private static void OnRestoreAutoPanSettingsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (!(d is MapView))
|
||||
throw new InvalidOperationException("This property must be attached to a mapview");
|
||||
|
||||
|
||||
MapView mv = (MapView)d;
|
||||
var oldValue = e.OldValue as RestoreAutoPanMode;
|
||||
if (oldValue != null)
|
||||
oldValue.DetachFromMapView(mv);
|
||||
var newValue = e.NewValue as RestoreAutoPanMode;
|
||||
if (newValue != null)
|
||||
newValue.AttachToMapView(mv);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -15,4 +15,7 @@
|
|||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Esri.ArcGISRuntime.WPF" Version="100.7.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
@ -41,7 +41,7 @@
|
|||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<StackPanel VerticalAlignment="Bottom" HorizontalAlignment="Left">
|
||||
<TextBlock Text="{Binding PrnNumber}"
|
||||
<TextBlock Text="{Binding Id}"
|
||||
FontWeight="Bold"
|
||||
HorizontalAlignment="Center" >
|
||||
<TextBlock.Foreground>
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@
|
|||
</StackPanel>
|
||||
</Ellipse.ToolTip>
|
||||
</Ellipse>
|
||||
<TextBlock Text="{Binding PrnNumber}" Margin="8,-8" />
|
||||
<TextBlock Text="{Binding Id}" Margin="8,-8" />
|
||||
</Canvas>
|
||||
</local:PolarPlacementItem>
|
||||
</DataTemplate>
|
||||
|
|
|
|||
Loading…
Reference in a new issue