322 lines
15 KiB
C#
322 lines
15 KiB
C#
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
|
||
}
|
||
}
|