如何基于串口实时经纬度,在gMap.net中绘制每秒更新的路线?
我之前也折腾过GMap.NET的实时轨迹绘制,刚好能解决你的问题!核心就是要搞定异步串口数据接收和UI线程安全更新这两个点,给你一步步拆解:
1. 先搞定串口数据的异步接收
串口接收是阻塞操作,绝对不能在UI线程里跑,不然地图会直接卡成PPT。我一般用后台线程监听串口,解析出经纬度后存到线程安全的列表里:
private SerialPort _gpsSerialPort; private List<PointLatLng> _liveRoutePoints = new List<PointLatLng>(); private readonly object _pointLock = new object(); // 用来避免多线程冲突的锁 // 初始化串口,根据你的实际设备配置调整参数 private void SetupSerialPort() { _gpsSerialPort = new SerialPort("COM3", 9600, Parity.None, 8, StopBits.One); _gpsSerialPort.DataReceived += OnSerialDataReceived; try { _gpsSerialPort.Open(); } catch (Exception ex) { MessageBox.Show($"串口打开失败:{ex.Message}"); } } // 串口数据接收事件(这个是在后台线程触发的,不能直接操作UI) private void OnSerialDataReceived(object sender, SerialDataReceivedEventArgs e) { string rawData = _gpsSerialPort.ReadLine().Trim(); // 这里要根据你的串口输出格式来解析,比如如果是NMEA协议的$GPGGA语句,要专门解析 // 假设你的数据格式是 "Lat:39.9042,Lng:116.4074",自行调整解析逻辑 var dataParts = rawData.Split(','); if (dataParts.Length == 2 && double.TryParse(dataParts[0].Split(':')[1], out double latitude) && double.TryParse(dataParts[1].Split(':')[1], out double longitude)) { lock (_pointLock) { _liveRoutePoints.Add(new PointLatLng(latitude, longitude)); } } }
2. 每秒定时更新地图路线
用Windows Forms的Timer(它在UI线程触发,安全)来每秒拉取一次最新的点位,更新GMap.NET的路线:
private Timer _mapUpdateTimer; // 初始化定时器,设置1秒间隔 private void SetupUpdateTimer() { _mapUpdateTimer = new Timer { Interval = 1000 // 1000毫秒=1秒 }; _mapUpdateTimer.Tick += OnMapUpdateTick; _mapUpdateTimer.Start(); } // 定时器触发的UI更新逻辑 private void OnMapUpdateTick(object sender, EventArgs e) { if (_liveRoutePoints.Count == 0) return; // 线程安全地复制当前的点位列表,避免在更新时被后台线程修改 List<PointLatLng> currentPoints; lock (_pointLock) { currentPoints = new List<PointLatLng>(_liveRoutePoints); // 可选:如果不需要保留全部历史轨迹,可以在这里清空列表,只显示最新的一段 // _liveRoutePoints.Clear(); } // 更新地图上的路线 gMapControl1.Overlays.RemoveAll(o => o.Id == "liveRoute"); // 先移除旧的路线图层 var routeOverlay = new GMapOverlay("liveRoute"); var liveRoute = new GMapRoute(currentPoints, "实时轨迹") { Stroke = new Pen(Color.DarkRed, 3), // 设置路线颜色和粗细 IsHitTestVisible = false }; routeOverlay.Routes.Add(liveRoute); gMapControl1.Overlays.Add(routeOverlay); // 可选:自动将地图中心定位到最新的点位 if (currentPoints.Count > 0) { gMapControl1.Position = currentPoints.Last(); } }
3. 初始化和资源清理
在窗体加载时完成所有初始化,关闭时记得释放资源:
private void MainForm_Load(object sender, EventArgs e) { // 初始化GMap.NET基础配置 gMapControl1.MapProvider = GMapProviders.GoogleMap; gMapControl1.MinZoom = 5; gMapControl1.MaxZoom = 20; gMapControl1.Zoom = 15; gMapControl1.ShowCenter = false; // 可以关掉默认的中心点标记 // 启动串口监听和定时器 SetupSerialPort(); SetupUpdateTimer(); } private void MainForm_FormClosing(object sender, FormClosingEventArgs e) { // 释放资源 _mapUpdateTimer?.Stop(); _mapUpdateTimer?.Dispose(); _gpsSerialPort?.Close(); _gpsSerialPort?.Dispose(); }
几个关键注意点
- 线程安全第一:后台线程存点位,UI线程读点位,一定要用锁保护列表,不然会出现奇怪的崩溃或者数据错乱。
- 适配你的串口格式:如果是标准GPS模块,输出的是NMEA协议(比如$GPGGA),那你需要专门解析这类语句,提取经纬度(注意NMEA的经纬度格式是度分格式,要转成十进制)。
- 性能优化:如果运行时间很长,点位会越来越多,建议定时清理旧点位,比如只保留最近500个点,避免内存占用过高。
- 异常处理:串口操作很容易出问题(比如设备断开),一定要加try-catch捕获异常,避免程序直接崩溃。
内容的提问来源于stack exchange,提问作者Okenite
相关产品推荐
相关产品推荐

