using GraduatedCylinder; using GraduatedCylinder.Geo; using GraduatedCylinder.Geo.Gps; using GraduatedCylinder.Geo.Gps.Nmea; using Nmea.Core0183; using Shouldly; using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Threading; namespace XiaoZhiSharp.Kernels.Gis { /* --NMEA0183协议--RMC,RMC,GGA,GSA,GSV,PVTG,GLL代表意义-- |----------------------------------------------------------------------------------------------------| |NMEA0183 常用语句类型说明 | |----------------------------------------------------------------------------------------------------| |语句类型 | 全称 | 含义说明 | |RMC | Recommended Minimum Specific GNSS Data | 推荐最小定位信息 | | | | - 包含最基本的位置、速度、时间和日期信息 | | | | - 最常用的定位数据语句 | |---------|-------------------------------------------|----------------------------------------------| |GGA | Global Positioning System Fix Data | GPS定位信息 | | | | - 包含详细的定位质量信息 | | | | - 经纬度、海拔高度、GPS质量指示、卫星数量等| |---------|-------------------------------------------|----------------------------------------------| |GSA | GNSS DOP and Active Satellites | 卫星精度与活动卫星 | | | | - 显示当前使用的卫星和精度因子(DOP) | | | | - 定位模式(2D/3D)、PDOP、HDOP、VDOP值 | |---------|-------------------------------------------|----------------------------------------------| |GSV | GNSS Satellites in View | 可见卫星信息 | | | | - 列出所有可见卫星的详细信息 | | | | - 卫星编号、仰角、方位角、信噪比(SNR)等 | |---------|-------------------------------------------|----------------------------------------------| |VTG | Course Over Ground and Ground Speed | 地面速度与航向 | | | | - 对地速度和对地航向信息 | | | | - 真实航向、磁航向、速度(节、公里/小时) | |---------|-------------------------------------------|----------------------------------------------| |GLL | Geographic Position - Latitude/Longitude | 地理经纬度位置 | | | | - 纯粹的经纬度坐标信息 | | | | - 包含定位状态(有效/无效)和时间戳 | |----------------------------------------------------------------------------------------------------| */ public class SharpGISNmeaParser { /// /// 实时GPS数据采集方法 - 从COM4串口读取GPS数据并记录到日志文件 /// public static void LiveGpsOnCOM4() { // GPS日志文件路径 string fileName = @"C:\Gps\Test.glog"; // 如果日志文件已存在,则删除旧文件以确保重新开始记录 if (File.Exists(fileName)) { File.Delete(fileName); } // 创建NMEA串口数据提供者,指定COM4端口 // NMEA (National Marine Electronics Association) 0183是GPS设备通信标准协议 IProvideSentences sentences = new NmeaSerialPort( portName: "COM4", baudRate: 4800, parity: System.IO.Ports.Parity.None, dataBits: 8, stopBits: System.IO.Ports.StopBits.One); // 包装数据提供者,添加日志记录功能,所有接收到的GPS语句将被记录到指定文件 sentences = new SentenceLogger(sentences, fileName); // 创建GPS单元实例,用于解析NMEA语句并生成GPS数据事件 GpsUnit gps = new(sentences); // 注册位置变化事件处理程序 // 当GPS设备报告新的位置数据时触发此事件 gps.LocationChanged += Gps_LocationChanged; // 启用GPS数据采集,开始监听串口数据并触发事件 gps.IsEnabled = true; // 数据采集循环 - 保持GPS运行10秒(100次 × 100毫秒) for (int i = 0; i < 100; i++) { Thread.Sleep(100); // 每次休眠100毫秒 } // 禁用GPS数据采集,停止监听数据 gps.IsEnabled = false; // 验证日志文件是否成功创建 // 如果文件不存在,说明GPS数据记录过程中出现问题 if (!File.Exists(fileName)) { throw new FileNotFoundException("Expected test to create a log file.", fileName); } } private static void Gps_LocationChanged(LocationChangedEventArgs args) { // 记录当前系统时间和GPS消息时间戳 Trace.TraceInformation("Received: {0:u}: Message Timestamp: {1:u}", DateTime.Now, args.Time); // 输出位置信息:纬度、经度、海拔高度(转换为英尺显示,保留1位小数) Trace.TraceInformation(" Lat: {0} Long: {1} Alt: {2}", args.Position.Latitude, // 纬度坐标 args.Position.Longitude, // 经度坐标 args.Position.Altitude.ToString(LengthUnit.Foot, 1)); // 海拔高度,单位:英尺 // 输出航向和速度信息 Trace.TraceInformation(" Heading: {0} at Speed: {1}", args.Heading, // 航向角度 args.Speed.ToString(SpeedUnit.MilesPerHour)); // 速度,单位:英里/小时 Trace.TraceInformation(""); // 输出空行分隔不同的位置更新 } /// /// 以最快速度回放GPS日志文件,用于测试GPS数据处理性能和事件触发机制 /// 该方法会模拟GPS数据播放,验证在高速数据输入情况下的处理能力 /// public static void PlaybackLogAsFastAsPossible() { // 记录接收到的事件数量 int eventCount = 0; // 指定要回放的GPS日志文件路径 // @前缀表示逐字字符串,避免转义字符处理 string fileName = @".\Configs\Sample1.gpslog"; // 创建SentenceLog实例用于解析和回放GPS日志文件 // PlaybackRate.AsFastAsPossible表示以最大速度回放,不按实际时间间隔 SentenceLog sentences = new(fileName, SentenceLog.PlaybackRate.AsFastAsPossible); sentences.SentenceReceived += Sentences_SentenceReceived; // 创建GPS单元实例,传入日志回放器作为数据源 GpsUnit gps = new(sentences); // 注册位置改变事件处理程序 // 使用lambda表达式,每次事件触发时增加事件计数器 // _表示忽略事件参数,因为我们只需要计数 gps.LocationChanged += _ => eventCount++; gps.LocationChanged += Gps_LocationChanged; // 记录测试开始时间 DateTime startTime = DateTime.Now; // 启用GPS单元,开始接收和处理数据 // 这将启动数据流并开始触发LocationChanged事件 gps.IsEnabled = true; // 循环等待直到日志回放完成 // PlaybackComplete属性指示是否所有日志数据都已处理完毕 while (!sentences.PlaybackComplete) { // 每次循环暂停50毫秒,避免过度占用CPU资源 // 这给了系统处理事件和更新状态的时间 Thread.Sleep(50); } // 回放完成后禁用GPS单元,停止数据处理 gps.IsEnabled = false; // 计算整个回放过程的持续时间 TimeSpan duration = DateTime.Now - startTime; // 断言验证:期望接收到18个位置改变事件 // 这是基于测试数据文件的预期行为验证 eventCount.ShouldBe(18); // 断言验证:整个回放过程应该在1秒内完成 // 这确保了"最快速度"回放的性能要求 duration.ShouldBeLessThan(new TimeSpan(0, 0, 1)); } private static readonly GpsParser _parser = new(); private static readonly List _activeSatellitePrns = new(); private static readonly IProvideSentences _nmeaProvider; private static readonly Dictionary _satellites = new(); public static GpsFixType CurrentFixType { get; private set; } public static GeoPosition CurrentLocation { get; private set; } public static double PositionDop { get; private set; } public static IEnumerable Satellites => _satellites.Where(skv => _activeSatellitePrns.Contains(skv.Key)).Select(skv => skv.Value); public static double VerticalDop { get; private set; } public static DateTimeOffset CurrentTime { get; private set; } public static bool HasLocation { get; private set; } public static Heading CurrentHeading { get; private set; } public static double HorizontalDop { get; private set; } public static Speed MinimumSpeedForHeadingUpdate { get; set; } public static Speed CurrentSpeed { get; private set; } private static void Sentences_SentenceReceived(Sentence st) { Message? message = _parser.Parse(st); if (message == null) { return; } //NB set all values then raise all notifications if (message.Value is IProvideSatelliteInfo info) { foreach (SatelliteInfo satellite in info.Satellites) { _satellites[satellite.Prn] = satellite; } } if (message.Value is IProvideActiveSatellites active) { _activeSatellitePrns.Clear(); foreach (int prn in active.ActiveSatellitePrns.Where(prn => prn != 0)) { _activeSatellitePrns.Add(prn); } } if (message.Value is IProvideFixType fixType) { CurrentFixType = fixType.CurrentFix; } if (message.Value is IProvideDilutionOfPrecision dop) { PositionDop = dop.PositionDop; HorizontalDop = dop.HorizontalDop; VerticalDop = dop.VerticalDop; } if (message.Value is IProvideTime time) { CurrentTime = time.CurrentTime; } if (message.Value is IProvideTrajectory trajectory) { //NB heading and speed are correlated CurrentSpeed = trajectory.CurrentSpeed; // NB don't update heading when speed is near zero if (CurrentSpeed > MinimumSpeedForHeadingUpdate) { CurrentHeading = trajectory.CurrentHeading; } } if (message.Value is IProvideGeoPosition position) { GeoPosition newLocation = position.CurrentLocation; CurrentLocation = newLocation.Altitude == Length.Unknown ? new GeoPosition(newLocation.Latitude, newLocation.Longitude, CurrentLocation.Altitude) : newLocation; HasLocation = true; } var sats = Satellites; var satelliteType = AnalysisSatelliteType(message.Sentence.Id); Console.WriteLine($"NMEA句子标识符:{message.Sentence.Id}"); Console.WriteLine($"卫星类型:{satelliteType},卫星数量:{_satellites.Count},使用卫星数量;{sats.Count()}"); } /// /// 根据NMEA句子标识符分析卫星类型 /// /// NMEA句子标识符 /// private static SatelliteType AnalysisSatelliteType(string sentenceId) { SatelliteType satelliteType = SatelliteType.GPS; switch (sentenceId) { case "$GP": satelliteType = SatelliteType.GPS; break; case "$GL": satelliteType = SatelliteType.GLONASS; break; case "$GA": satelliteType = SatelliteType.Galileo; break; case "$GN": satelliteType = SatelliteType.GNSS; break; case "$BD": satelliteType = SatelliteType.BDS; break; } return satelliteType; } } public enum SatelliteType { /// /// Global Positioning System(全球定位系统) /// GPS, /// /// Global Navigation Satellite System(全球导航卫星系统) /// GLONASS, /// /// European Global Satellite Navigation System(伽利略卫星导航系统) /// Galileo, /// /// Combined GPS, GLONASS, Galileo, etc.(组合导航系统) /// GNSS, /// /// BeiDou Navigation Satellite System(北斗卫星导航系统) /// BDS } }