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
}
}