2025-10-11 18:25:59 +08:00

322 lines
15 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
{
/// <summary>
/// 实时GPS数据采集方法 - 从COM4串口读取GPS数据并记录到日志文件
/// </summary>
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(""); // 输出空行分隔不同的位置更新
}
/// <summary>
/// 以最快速度回放GPS日志文件用于测试GPS数据处理性能和事件触发机制
/// 该方法会模拟GPS数据播放验证在高速数据输入情况下的处理能力
/// </summary>
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<int> _activeSatellitePrns = new();
private static readonly IProvideSentences _nmeaProvider;
private static readonly Dictionary<int, SatelliteInfo> _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<SatelliteInfo> 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()}");
}
/// <summary>
/// 根据NMEA句子标识符分析卫星类型
/// </summary>
/// <param name="sentenceId">NMEA句子标识符</param>
/// <returns></returns>
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
{
/// <summary>
/// Global Positioning System全球定位系统
/// </summary>
GPS,
/// <summary>
/// Global Navigation Satellite System全球导航卫星系统
/// </summary>
GLONASS,
/// <summary>
/// European Global Satellite Navigation System伽利略卫星导航系统
/// </summary>
Galileo,
/// <summary>
/// Combined GPS, GLONASS, Galileo, etc.(组合导航系统)
/// </summary>
GNSS,
/// <summary>
/// BeiDou Navigation Satellite System北斗卫星导航系统
/// </summary>
BDS
}
}