From 22db351eff44b3309c47893e435312fc72267322 Mon Sep 17 00:00:00 2001 From: Morten Nielsen <1378165+dotMorten@users.noreply.github.com> Date: Fri, 28 Aug 2020 20:12:46 -0700 Subject: [PATCH] Update example to use the new GnssMonitor --- docs/concepts/ArcGISRuntime.md | 189 +++++++++++++-------------------- 1 file changed, 76 insertions(+), 113 deletions(-) diff --git a/docs/concepts/ArcGISRuntime.md b/docs/concepts/ArcGISRuntime.md index c8e8b76..09fb35c 100644 --- a/docs/concepts/ArcGISRuntime.md +++ b/docs/concepts/ArcGISRuntime.md @@ -54,137 +54,100 @@ namespace NmeaParser.ArcGIS ``` ### Combining multiple NMEA messages into a single location event -NMEA often happens in a burst of messages, which could be combined to one larger location object with more information available. -By relying on the time stamp in most of the messages, we can combine them all to get better metadata about the location. +NMEA often happens in a burst of messages, which could be combined to one larger location object with more information available, as well as containing information from multiple different satellite systems. +By using the `GnssMonitor` class that aggregates these messages, we can create a much more robust location provider: ``` using System; -using System.Linq; using System.Threading.Tasks; using Esri.ArcGISRuntime.Geometry; -using Esri.ArcGISRuntime.Location; -using NmeaParser; -using NmeaParser.Messages; +using NmeaParser.Gnss; namespace NmeaParser.ArcGIS { - public class NmeaLocationProvider : LocationDataSource + public class NmeaLocationDataSource : Esri.ArcGISRuntime.Location.LocationDataSource { - private readonly NmeaParser.NmeaDevice device; - private Gga lastGga; - private Rmc lastRmc; - private Gsa lastGsa; - private Gst lastGst; + private static SpatialReference wgs84_ellipsoidHeight = SpatialReference.Create(4326, 115700); + private readonly GnssMonitor m_gnssMonitor; + private readonly bool m_startStopDevice; + private double lastCourse = 0; // Course can fallback to NaN, but ArcGIS Datasource don't allow NaN course, so we cache last known as a fallback - public NmeaLocationProvider(NmeaParser.NmeaDevice device) + /// + /// Initializes a new instance of the class. + /// + /// The NMEA device to monitor + /// Whether starting this datasource also controls the underlying NMEA device + public NmeaLocationDataSource(NmeaParser.NmeaDevice device, bool startStopDevice = true) : this(new GnssMonitor(device), startStopDevice) { - this.device = device; - device.MessageReceived += NmeaMessageReceived; } - private void NmeaMessageReceived(object sender, NmeaMessageReceivedEventArgs e) + + /// + /// Initializes a new instance of the class. + /// + /// The NMEA device to monitor + /// Whether starting this datasource also controls the underlying NMEA device + public NmeaLocationDataSource(NmeaParser.Gnss.GnssMonitor monitor, bool startStopDevice = true) { - var message = e.Message; - bool newFix = false; - if (message is Rmc rmc && rmc.Active) - { - lastRmc = rmc; - newFix = true; - } - else if (message is Gga gga) - { - lastGga = gga; - newFix = true; - } - else if (message is Gst gst) - { - lastGst = gst; - newFix = true; - } - else if (message is Gsa gsa) - { - lastGsa = gsa; - } + if (monitor == null) + throw new ArgumentNullException(nameof(monitor)); + this.m_gnssMonitor = monitor; + m_startStopDevice = startStopDevice; + } + + protected async override Task OnStartAsync() + { + m_gnssMonitor.LocationChanged += OnLocationChanged; + m_gnssMonitor.LocationLost += OnLocationChanged; + if (m_startStopDevice && !this.m_gnssMonitor.Device.IsOpen) + await this.m_gnssMonitor.Device.OpenAsync(); + + if (m_gnssMonitor.IsFixValid) + OnLocationChanged(this, EventArgs.Empty); + } + + protected override Task OnStopAsync() + { + m_gnssMonitor.LocationChanged -= OnLocationChanged; + m_gnssMonitor.LocationLost -= OnLocationChanged; + if(m_startStopDevice) + return m_gnssMonitor.Device.CloseAsync(); else - { - return; - } - // We require the timestamps to match to raise them together. Gsa doesn't have a time stamp so just using latest for that - TimeSpan? timeOfFixMax = MaxTime(lastRmc?.FixTime.TimeOfDay, lastGga?.FixTime, lastGst?.FixTime); - TimeSpan? timeOfFixMin = MinTime(lastRmc?.FixTime.TimeOfDay, lastGga?.FixTime, lastGst?.FixTime); - if (newFix && timeOfFixMax == timeOfFixMin) - { - var location = NmeaLocation.Create(timeOfFixMax.Value, lastRmc, lastGga, lastGsa, lastGst); - if (location != null) - base.UpdateLocation(location); - } + return Task.CompletedTask; } - private static TimeSpan? MaxTime(params TimeSpan?[] timeSpans) => timeSpans.Where(t => t != null).Max(); - private static TimeSpan? MinTime(params TimeSpan?[] timeSpans) => timeSpans.Where(t => t != null).Min(); - protected override Task OnStartAsync() => device.OpenAsync(); - protected override Task OnStopAsync() => device.CloseAsync(); - } + private Esri.ArcGISRuntime.Location.Location currentLocation; - /// - /// Custom location class with the additional NMEA information associated with it - /// - public class NmeaLocation : Location - { - private NmeaLocation(DateTimeOffset? timestamp, MapPoint position, double horizontalAccuracy, double verticalAccuracy, double velocity, double course, bool isLastKnown) - : base(timestamp, position, horizontalAccuracy, verticalAccuracy, velocity, course, isLastKnown) + private void OnLocationChanged(object sender, EventArgs e) { + if (double.IsNaN(m_gnssMonitor.Longitude) || double.IsNaN(m_gnssMonitor.Latitude)) return; + if (!double.IsNaN(m_gnssMonitor.Course)) + lastCourse = m_gnssMonitor.Course; + DateTimeOffset? timestamp = null; + if(m_gnssMonitor.FixTime.HasValue) + timestamp = new DateTimeOffset(DateTime.UtcNow.Date.Add(m_gnssMonitor.FixTime.Value)); + var location = new Esri.ArcGISRuntime.Location.Location( + timestamp: timestamp, + position: !double.IsNaN(m_gnssMonitor.Altitude) ? new MapPoint(m_gnssMonitor.Longitude, m_gnssMonitor.Latitude, m_gnssMonitor.Altitude, wgs84_ellipsoidHeight) : new MapPoint(m_gnssMonitor.Longitude, m_gnssMonitor.Latitude, SpatialReferences.Wgs84), + horizontalAccuracy: m_gnssMonitor.HorizontalError, + verticalAccuracy: m_gnssMonitor.VerticalError, + velocity: double.IsNaN(m_gnssMonitor.Speed) ? 0 : m_gnssMonitor.Speed * 0.51444444, + course: lastCourse, + !m_gnssMonitor.IsFixValid); + // Avoid raising additional location events if nothing changed + if (currentLocation == null || + currentLocation.Position.X != location.Position.X || + currentLocation.Position.Y != location.Position.Y || + currentLocation.Position.Z != location.Position.Z || + currentLocation.Course != location.Course || + currentLocation.Velocity != location.Velocity || + currentLocation.HorizontalAccuracy != location.HorizontalAccuracy || + currentLocation.VerticalAccuracy != location.VerticalAccuracy || + currentLocation.IsLastKnown != location.IsLastKnown || + timestamp != location.Timestamp) + { + currentLocation = location; + UpdateLocation(currentLocation); + } } - - public static NmeaLocation Create(TimeSpan timeOfFix, Rmc rmc, Gga gga, Gsa gsa, Gst gst) - { - MapPoint position = null; - double horizontalAccuracy = double.NaN; - double verticalAccuracy = double.NaN; - double velocity = 0; - double course = 0; - // Prefer GGA over RMC for location - if (gga != null && gga.FixTime == timeOfFix) - { - if (double.IsNaN(gga.Altitude)) - position = new MapPoint(gga.Longitude, gga.Latitude, SpatialReferences.Wgs84); - else - { - // Vertical id 115700 == ellipsoid reference system. Gga is geoid, but we subtract GeoidalSeparation to simplify - // vertical transformations from the simpler/better known ellipsoidal model - position = new MapPoint(gga.Longitude, gga.Latitude, gga.Altitude + gga.GeoidalSeparation, SpatialReference.Create(4326, 115700)); - } - } - if (rmc != null && rmc.FixTime.TimeOfDay == timeOfFix) - { - if (position == null) - { - position = new MapPoint(rmc.Longitude, rmc.Latitude, SpatialReferences.Wgs84); - } - velocity = double.IsNaN(rmc.Speed) ? 0 : rmc.Speed; - course = double.IsNaN(rmc.Course) ? 0 : rmc.Course; - } - if (gst != null && gst.FixTime == timeOfFix) - { - verticalAccuracy = gst.SigmaHeightError; - horizontalAccuracy = gst.SemiMajorError; - } - if (position == null) - return null; - var location = new NmeaLocation(DateTimeOffset.UtcNow.Date.Add(timeOfFix), position, horizontalAccuracy, verticalAccuracy, velocity, course, false); - location.Rmc = rmc; - location.Gga = gga; - location.Gsa = gsa; - location.Gst = gst?.FixTime == timeOfFix ? gst : null; - return location; - } - public Rmc Rmc { get; private set; } - public Gga Gga { get; private set; } - public Gsa Gsa { get; private set; } - public Gst Gst { get; private set; } - - public int NumberOfSatellites => Gga?.NumberOfSatellites ?? -1; - public double Hdop => Gsa?.Hdop ?? Gga?.Hdop ?? double.NaN; - public double Pdop => Gsa?.Pdop ?? double.NaN; - public double Vdop => Gsa?.Vdop ?? double.NaN; } } ```