ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

GMap.NET官方Demo实战:WinForm上位机地图定位与坐标转换

GMap.NET官方Demo实战:WinForm上位机地图定位与坐标转换 简介本资源是面向.NET开发者的GMap.NET地图集成实战Demo适用于Windows Forms与WPF桌面应用开发场景帮助初学者快速掌握地理信息可视化核心技能。压缩包共40个文件含13个C#源码文件如MainWindow.xaml.cs等、4个关键DLL依赖库、3个可执行程序exe、2个XAML界面定义及csproj/sln等工程配置文件完整呈现项目结构与运行入口5.45MB体积轻量易部署。已有562人学习下载体现其在GIS功能入门与快速验证中的实用价值。读者可直接运行示例程序直观体验地图服务切换、标记添加、路线规划、热力图渲染、多边形绘制及图层事件响应等十大核心功能并通过源码反向理解GMFlow框架下的坐标系统适配、异步加载机制与自定义地图服务接入逻辑是深入理解GMap.NET官方实例的优质实践素材。 我用了差不多一周的时间把GMap.NET官方那个Demo包翻来覆去地折腾了一遍。说实话这几年用C#做上位机和工控项目最麻烦的部分之一就是要在WinForm窗体上嵌入一张能实时显示设备位置的地图。你要么自己写一个瓦片下载器要么就得各种找控件、调兼容性。直到拿到GMAP.NETDemo.rar这个官方示例包很多问题才真正落地。这篇文章就从这份Demo出发把GMap.NET从图源加载、坐标偏移、图层管理到和串口/视觉联动的完整链路我习惯叫它GMFlow讲清楚重点聊聊大家最容易踩的坑。这篇文章适合谁看如果你正准备在WinForm或WPF上位机里加地图或者你做GPS定位、车辆监控、设备轨迹回放再或者你想把视觉检测结果叠加在地图上那这篇内容基本就是为你准备的。我会按官方Demo的脉络配合一段可直接复制的实时定位代码把GMap.NET从“能用”做到“好用”。1. GMap.NET是什么为什么新项目首选它1.1 同样是C#加地图几种方案选型对比我刚接触这一需求时脑子里其实同时闪过好几个方案。最“省事”的做法是放一个WebBrowser控件嵌入Leaflet或OpenLayers页面通过JS和C#互操作。但真正做起来你会发现WebBrowser内核老旧和WinForm之间的交互要么用ObjectForScripting要么自己去解析URL协议通信状态一多就乱而且用户机器上如果装了奇怪的IE插件页面表现会非常分裂。用地图厂商自带的SDK又是另一回事。百度、高德、天地图都有自己的JavaScript API或Android/iOS SDKWindows桌面端的官方SDK少得可怜而且授权方式、配额限制、离线能力都要仔细核对作为工控上位机的组件未必合适。GMap.NET这个开源项目能从一堆方案里跑出来核心就是它把地图这事儿拆得足够轻。整个库就是一组托管程序集核心是GMap.NET.Core和对应平台的GMap.NET.WinForms或GMap.NET.WPF。你不需要嵌入浏览器不需要申请业务域名白名单拖一个GMapControl控件到窗体上几行代码就能显示地图。它支持的图源是一个Provider抽象OpenStreetMap、Bing、ArcGIS等等都能切成同一套调用方式。更关键的是它自带瓦片缓存机制地图数据会落到本地SQLite缓存里对于经常在无外网环境跑的上位机来说预置缓存后完全离线也能用。这几类方案我整理成一个表格方便你按自己的场景权衡方案接入成本离线能力商用限制适用场景WebBrowser Leaflet中JS/C#交互繁琐弱需要自己设计预加载方案依赖地图服务商政策偏Web展示不适合桌面嵌入厂商SDK百度/高德等低到中要看平台支持中官方离线包一般给得少有配额和授权限制移动端或明确使用某家地图的Web项目GMap.NET低控件直接拖强自带SQLite缓存开源免费图源跟随Provider策略WinForm/WPF上位机、GPS监控、地图叠加业务数据这也是为什么我把GMap.NET作为首选。它不是什么“全功能GIS平台”但对于上位机里“显示底图、标记点位、画轨迹、标注区域”这四件事它提供了足够直白的方式官方Demo就是一个最好的学习入口。1.2 官方Demo包带我们少走哪些弯路拿到手的是GMAP.NETDemo.rar这个压缩包解压后不是一个单独的“能跑的Demo.exe”而是一整套解决方案。你会在里面看到Core工程、WinForms示例工程还有缓存处理相关的代码。很多人第一次打开会被吓一跳我只是想显示一张地图为什么要给我这么多工程其实这正是好事。官方Demo的价值不是给你一个黑盒而是把地图初始化的完整路线演示了一遍。你可以看到它是怎么配置Provider的是怎么在界面上放一个GMapControl并且把Overlay一层层加进去的是怎么设置缩放级别、中心点坐标的。这些代码本身就是一份极好的“使用说明书”。我推荐的做法是不要自己从头新建一个空窗体去琢磨“GMapControl从哪里来”先打开官方Demo把几个核心页面跑通。比如图源切换页面会列出所有支持的地图源Markers页面会演示不同样式的标记点Polygons页面能让你画区域。先把这些页面各点一遍你对GMap.NET的API印象会非常直观。然后你再对照源码去看这些操作背后对应的是哪些类这时候再写自己的项目思路就会顺很多。官方Demo里的GMFlow我理解就是“地图加载 - 图层管理 - 业务标记交互 - 数据刷新”这一条链路。看懂这条链路后面所有东西都是这条链路上的一个节点。2. 官方Demo快速跑通环境与初始化2.1 从GMAP.NETDemo.rar开始工程结构怎么看解压后建议你先看一下解决方案里有哪些项目。我拿到的版本里大概是这样的组织方式GMap.NET.Core核心库的源码包括Provider、缓存、地理坐标计算、网络请求等底层实现。GMap.NET.WinFormsWinForms控件工程GMapControl、Overlay、Marker、Route这些都在这里。GMap.NET.WindowsForms.Demo官方Demo主程序里面是各种示例页面。有一点要提醒如果你只是做应用开发不打算魔改底层就没必要把Core和WinForms工程加到自己的解决方案里。你可以直接编译官方Demo或者直接引用打包好的DLL。主程序里会依赖GMap.NET.Core.dll、GMap.NET.WinForms.dll把这两个程序集拷到你的工程目录同样能用。编译顺序上建议先编译Core再编译WinForms最后编译Demo主程序。如果用的是Visual Studio 2022打开sln后直接生成解决方案就行一般不会有特别刁钻的报错。如果你拿到的版本里有针对不同目标框架的分支配置就按照自己的.NET版本去选对应的输出目录。这里特别说一句GMap.NET有几个版本分支如果你在NuGet里搜GMap.NET会看到GMap.NET.WinForms、GMap.NET.WPF、GMap.NET.WinForms.Independent等不同包建议优先用官方GitHub Releases里提供的那一套版本最稳定和Demo包匹配度也最高。2.2 第一次加载地图的配置和代码环境跑通之后你就有了一个可以直接操作的地图控件。新建一个WinForm项目把GMapControl从工具箱拖到窗体上把它命名成mapControl。然后写Load事件初始化地图。private void Form1_Load(object sender, EventArgs e) { // 选择一个可用且稳定的地图源 mapControl.MapProvider GMapProviders.OpenStreetMapProvider; // 缩放范围限制避免用户误操作缩到太空 mapControl.MinZoom 2; mapControl.MaxZoom 20; // 初始缩放级别15大约对应城市街区级别 mapControl.Zoom 15; // 中心点坐标这里先用上海人民广场附近演示 mapControl.Position new PointLatLng(31.2304, 121.4737); // 鼠标左键拖拽地图这个设置更符合常见地图交互习惯 mapControl.DragButton MouseButtons.Left; // 隐藏中心十字线 mapControl.ShowCenter false; // 关闭地图拖拽惯性避免在慢速工控机上出现拖影 mapControl.MapScaleInfoEnabled false; }这几行代码就是GMap.NET的“Hello World”。MapProvider决定底图来源Position是中心点坐标Zoom是缩放级别。你可能会注意到GMapControl上还有一堆属性比如CanDragMap、ScaleMode、EmptyTileColor这些可以等真正用到时再看文档初期先把地图显示出来最重要。需要提醒的是Position里用的坐标是WGS84经纬度。第一次加载时如果网络能正常访问地图服务地图会慢慢显示出来。如果一直是空白网格八成是网络访问不到图源服务器或者缓存目录写不进去。后面的问题排查章节我会展开聊。3. 核心功能拆解图源、坐标、图层3.1 图源机制与Provider切换GMap.NET把“从哪里取瓦片”这件事抽象成了GMapProvider类。每个图源就是它的一个子类实例在GMapProviders静态类里集中暴露。你写代码时只需要切换MapProvider这个属性地图的下载、缓存、拼接逻辑完全不用改。官方Demo里经常会看到类似这样的一段代码// 在界面下拉框里选择地图源 private void comboBoxMapProvider_SelectedIndexChanged(object sender, EventArgs e) { if (comboBoxMapProvider.SelectedItem is GMapProvider provider) { mapControl.MapProvider provider; mapControl.Zoom mapControl.MinZoom; // 切换图源后务必重置缩放否则可能会空白 } }关于图源选择我的经验是开发调试阶段用OpenStreetMap就够了因为它不需要API Key公网环境一般都能访问瓦片风格也很干净。如果项目要正式部署到生产环境必须先把目标网络的访问情况测清楚。在线地图服务属于外部依赖万一现场网络做了限制或者图源服务不稳定地图就会加载不出来。最稳妥的做法是“在线开发离线部署”开发时有网络正常下载瓦片让GMap.NET把瓦片缓存到本地SQLite文件部署时把这些缓存文件一并拷到现场机器上MapProvider设置的图源即使现场访问不到控件也会优先从缓存里读瓦片。这个离线方案在工控场景里非常实用。比如车间里一台设备异常要在监控大屏上显示它所在的地理位置现场机器可能根本不动外网但你预先缓存了那一带的瓦片GMap.NET依然能把地图显示得和在线时一样。3.2 坐标偏移问题GPS数据显示偏了怎么办这是我自己项目里踩得最深的一个坑也是GMap.NET论坛里被问烂的问题GPS返回的坐标没问题但标记点放在地图上就是差几百米。原因在于坐标系不统一。GPS设备输出的原始坐标是WGS84这是全球统一的经纬度坐标系。而国内很多在线图源为了合规要求对地图数据做了非线性偏移公开的地图服务往往基于GCJ02坐标。你拿着WGS84坐标直接画到偏移后的地图底图上自然就偏出去了。GMap.NET内部不会自动帮你做这套坐标转换它默认你给的坐标就是和当前图源一致的坐标。所以你需要自己写一个转换函数在业务数据和地图显示之间做一道翻译。比较常见的做法是把GPS的WGS84坐标转成GCJ02再丢给地图。我贴一段我项目里用过的转换方法基于公开的坐标转换算法直接拷进去就能用public static class CoordConverter { private const double A 6378245.0; private const double EE 0.00669342162296594323; public static PointLatLng WGS84ToGCJ02(PointLatLng wgsCoord) { double lat wgsCoord.Lat; double lng wgsCoord.Lng; if (OutsideChina(lng, lat)) { return new PointLatLng(lat, lng); } double dLat TransformLat(lng - 105.0, lat - 35.0); double dLng TransformLng(lng - 105.0, lat - 35.0); double radLat lat / 180.0 * Math.PI; double magic Math.Sin(radLat); magic 1 - EE * magic * magic; double sqrtMagic Math.Sqrt(magic); dLat (dLat * 180.0) / ((A * (1 - EE)) / (magic * sqrtMagic) * Math.PI); dLng (dLng * 180.0) / (A / sqrtMagic * Math.Cos(radLat) * Math.PI); return new PointLatLng(lat dLat, lng dLng); } private static double TransformLat(double x, double y) { double ret -100.0 2.0 * x 3.0 * y 0.2 * y * y 0.1 * x * y 0.2 * Math.Sqrt(Math.Abs(x)); ret (20.0 * Math.Sin(6.0 * x * Math.PI) 20.0 * Math.Sin(2.0 * x * Math.PI)) * 2.0 / 3.0; ret (20.0 * Math.Sin(y * Math.PI) 40.0 * Math.Sin(y / 3.0 * Math.PI)) * 2.0 / 3.0; ret (160.0 * Math.Sin(y / 12.0 * Math.PI) 320.0 * Math.Sin(y * Math.PI / 30.0)) * 2.0 / 3.0; return ret; } private static double TransformLng(double x, double y) { double ret 300.0 x 2.0 * y 0.1 * x * x 0.1 * x * y 0.1 * Math.Sqrt(Math.Abs(x)); ret (20.0 * Math.Sin(6.0 * x * Math.PI) 20.0 * Math.Sin(2.0 * x * Math.PI)) * 2.0 / 3.0; ret (20.0 * Math.Sin(x * Math.PI) 40.0 * Math.Sin(x / 3.0 * Math.PI)) * 2.0 / 3.0; ret (150.0 * Math.Sin(x / 12.0 * Math.PI) 300.0 * Math.Sin(x / 30.0 * Math.PI)) * 2.0 / 3.0; return ret; } private static bool OutsideChina(double lng, double lat) { return lng 72.004 || lng 137.8347 || lat 0.8293 || lat 55.8271; } }这里有个生活化的类比WGS84坐标就好比你在图纸上量的实际厘米数而GCJ02底图是一张被拉伸过的图纸你按厘米数标点肯定会偏。转换公式就是帮你换算成“被拉伸图纸上的刻度”。实际项目里这套算法能解决绝大多数GPS显示偏移问题。如果你的业务场景涉及跨海、远洋这种超出国界范围的坐标函数里的OutsideChina判断会直接跳过转换避免在海外区域错误偏移。3.3 用Overlay管理Marker、Route、PolygonGMap.NET里所有要画到地图上的东西都不是直接塞给GMapControl的而是先放进一个GMapOverlay再把Overlay加到地图控件的Overlays集合里。这个设计和PS里的图层概念很像。你可以在一个Overlay里放车辆位置标记在另一个Overlay里放历史轨迹在第三个Overlay里放电子围栏区域。业务上需要显示或隐藏哪类数据只需要控制对应的Overlay不需要一个一个Marker去处理。比如我要在地图上加一个车辆标记private GMapOverlay _carOverlay; private GMapMarker _carMarker; private void InitCarMarker() { _carOverlay new GMapOverlay(carLayer); _carMarker new GMarkerGoogle(new PointLatLng(31.2304, 121.4737), GMarkerGoogleType.red); _carMarker.ToolTipText 车辆A; _carMarker.ToolTipMode MarkerTooltipMode.OnMouseOver; _carOverlay.Markers.Add(_carMarker); mapControl.Overlays.Add(_carOverlay); }GMapMarker本身是抽象类官方Demo里提供了GMarkerGoogle这种现成实现它的图标是一组彩色图钉。如果你不想用图钉样式也可以写一个类继承GMapMarker重写它的绘制逻辑画一个旋转矩形或者位图这在显示AGV小车、机器人的朝向时很有用。画轨迹线我用GMapRouteprivate GMapRoute _route; private ListPointLatLng _trackPoints new ListPointLatLng(); private void AddTrackPoint(PointLatLng point) { _trackPoints.Add(point); if (_route null) { _route new GMapRoute(_trackPoints, track); _route.Stroke new Pen(Color.Red, 2); _carOverlay.Routes.Add(_route); } else { _route.Points _trackPoints; // 更新轨迹点集 mapControl.Refresh(); // 触发重绘 } }画多边形区域比如电子围栏用GMapPolygonprivate void AddFence() { ListPointLatLng fencePoints new ListPointLatLng { new PointLatLng(31.2304, 121.4700), new PointLatLng(31.2304, 121.4760), new PointLatLng(31.2340, 121.4760), new PointLatLng(31.2340, 121.4700) }; GMapPolygon fence new GMapPolygon(fencePoints, fence); fence.Fill new SolidBrush(Color.FromArgb(50, Color.Blue)); fence.Stroke new Pen(Color.Blue, 2); _carOverlay.Polygons.Add(fence); }这套Overlay机制是GMFlow里最核心的一环地图是底图Overlay是图层Marker/Route/Polygon是图层里的元素。你只要维持好“元素只属于自己图层图层可以整体显隐”这个心智模型后续不管加多少业务数据代码都不会乱。4. 实战做一个GPS实时定位Demo4.1 模拟设备上报的逻辑和界面设计光看官方Demo不过瘾我一般会把它扩展成一个带实时刷新的小Demo。这个场景也很贴合实际设备每秒钟上报一次GPS位置上位机收到后在界面上显示设备当前位置同时划出它的移动轨迹。我设计这个Demo时用了一个Timer来模拟设备上报每500毫秒把设备坐标朝某个方向移动一小段然后刷新地图上的Marker位置和轨迹。真正接串口或PLC时把这个Timer触发改成串口数据接收事件或PLC轮询回调就行界面刷新逻辑不用变。事先强调一点Timer.Tick是在UI线程上执行的直接更新GMapControl没有跨线程问题但如果你的数据来自单独的通信线程那更新控件前必须通过Invoke切回UI线程否则会抛跨线程异常。这个细节后面第5章会细说。界面布局很简单一个GMapControl占满窗体窗体底部加一个状态Label显示当前坐标。为了效果更直观我再加一个CheckBox勾选后地图自动跟随设备移动。4.2 核心代码与GMFlow链路分析下面是这个Demo的核心逻辑。窗体字段与初始化public partial class FormGpsSimulator : Form { private GMapOverlay _deviceOverlay; private GMapMarker _deviceMarker; private GMapRoute _trackRoute; private ListPointLatLng _trackPoints new ListPointLatLng(); // 模拟的设备坐标起点在上海人民广场附近 private double _simLat 31.2304; private double _simLng 121.4737; private bool _isFirstFix true; private System.Windows.Forms.Timer _timer; public FormGpsSimulator() { InitializeComponent(); _timer new System.Windows.Forms.Timer(); _timer.Interval 500; _timer.Tick Timer_Tick; } private void FormGpsSimulator_Load(object sender, EventArgs e) { mapControl.MapProvider GMapProviders.OpenStreetMapProvider; mapControl.MinZoom 2; mapControl.MaxZoom 20; mapControl.Zoom 16; mapControl.DragButton MouseButtons.Left; mapControl.ShowCenter false; _deviceOverlay new GMapOverlay(deviceLayer); _deviceMarker new GMarkerGoogle(new PointLatLng(_simLat, _simLng), GMarkerGoogleType.red); _deviceOverlay.Markers.Add(_deviceMarker); mapControl.Overlays.Add(_deviceOverlay); // 地图中心先对准起始点 mapControl.Position new PointLatLng(_simLat, _simLng); _timer.Start(); } }模拟坐标变化并刷新界面private void Timer_Tick(object sender, EventArgs e) { // 模拟设备沿东北方向缓慢移动每次移动大约0.0001度 _simLat 0.0001; _simLng 0.0001; PointLatLng currentPoint new PointLatLng(_simLat, _simLng); if (_isFirstFix) { _deviceMarker.Position currentPoint; _isFirstFix false; } _deviceMarker.Position currentPoint; // 记录轨迹 _trackPoints.Add(currentPoint); if (_trackRoute null) { _trackRoute new GMapRoute(_trackPoints, track); _trackRoute.Stroke new Pen(Color.OrangeRed, 2); _deviceOverlay.Routes.Add(_trackRoute); } else { _trackRoute.Points _trackPoints; } // 如果勾选了跟随设备就移动地图中心 if (checkBoxFollow.Checked) { mapControl.Position currentPoint; } // 更新状态栏 labelCoord.Text $设备坐标: {_simLat:F5}, {_simLng:F5}; }运行起来后你会看到红色图钉在移动身后拖着一条橙红色轨迹。如果你勾选了跟随地图会始终以设备为中心滚动。这就是一个最简版的车/船/AGV实时监控页面模型。这段代码里有一个可以优化的点每次Timer Tick都把整个_route的Points重新赋一遍会触发整条轨迹重绘。如果设备运行了一天轨迹点可能上万个此时绘制效率会下降。实际项目里可以用抽稀策略每隔N个点存一个轨迹点或者把轨迹点分段成多条Route每段只维护几百个点。这也是GMFlow里“数据流”和“界面刷新”解耦需要思考的地方。5. 常见问题排查与避坑记录5.1 高频报错与处理方案速查表我在GMap.NET项目里跑出过不少问题也在群里看别人踩过不少整理成一张速查表遇到问题直接按图索骥。现象可能原因解决方案地图一直是灰色/白色网格网络访问不到图源服务器首次加载无缓存换可用图源检查外网连通性预置离线缓存首次加载非常慢瓦片下载没有并发控制或网络慢调大并发数等缓存建立后二次加载会快部署时带缓存报错“无法加载一个或多个请求的类型”多个GMap.NET版本程序集混用项目目标框架不一致删除所有旧DLL统一NuGet包版本重新生成并清理解决方案SQLite缓存异常地图频繁闪烁CacheLocation指向了无权限目录缓存文件损坏设置自定义缓存目录例如C:\MapCache关闭后删除损坏的Cache文件夹跨线程操作GMapControl时报错通信线程直接更新了控件使用InvokeRequired判断通过Invoke切回UI线程GPS坐标显示偏几百米使用了WGS84坐标地图底图是GCJ02写坐标转换函数统一转成底图坐标系GMapMarker不显示Marker没加到Overlay或者Overlay没加到地图检查Overlay.Markers.Add和mapControl.Overlays.Add是否都执行了切换图源后地图空白不同图源缩放级别对不上切换Provider后把Zoom重设为MinZoom再放大到目标级别程序内存只增不减Marker和Route只Add不RemoveTimer未释放事件未取消订阅关闭时清空Overlays、Dispose Timer、取消订阅事件5.2 几个值得记住的经验除了上面这些“显性”问题还有几个隐性经验是常规文档里不会写太细的。第一个是缓存目录。GMap.NET默认把瓦片缓存放在本机用户目录下但你在项目里如果用了自定义部署环境最好在程序启动时主动指定缓存路径。原因很简单一是用户目录可能被清理工具当成临时文件删掉删了之后地图又得全量下载二是如果你想做离线部署指定一个明确的缓存目录拷文件的时候不会漏。设置方式是一行代码mapControl.CacheLocation D:\MapCache;注意这个属性需要在加载地图前设置而且设置后不要在中途频繁改动否则某些版本会重建缓存索引。第二个是目标框架的选择。GMap.NET不同版本对.NET版本的支持不一样。如果你用的是.NET Framework 4.6.2就找对应版本的程序集如果你用.NET 6/8需要找支持net6.0-windows或net8.0-windows的包。项目里最常见的“DLL引用报错”基本都源于这里一个解决方案里同时引用了两套不同目标框架的GMap.NET然后互相干扰。另外WinForms项目如果要跑在64位系统上项目平台目标记得设成x64或AnyCPU但如果你还引用了32位原生DLL比如某些摄像头SDK那就要统一成x86策略这个统一性对工控机尤为重要。第三个是“缩放级别和底图清晰度”的关系。GMap.NET的MaxZoom看起来可以设很高但实际能显示多清晰取决于图源是否提供对应级别的瓦片。OpenStreetMap在20级有些区域就是模糊的这不是控件问题是图源本身没有更高精度的数据。在项目里不要盲目设太高MaxZoom要根据现场实际观察结果来定比如设到18或19级更合理。6. 在地图基础上做业务扩展6.1 串口/PLC数据如何驱动地图实时定位Demo的模拟Timer换成真实数据源是项目落地时最关键的一步。做上位机的朋友都知道串口数据来的时候是在一个独立的接收线程里不能直接去动UI控件。我的习惯是在串口接收事件里只做“数据解析”和“发布数据事件”然后让窗体订阅这个事件在事件处理器里调用Invoke更新地图。比如你有一个GpsData类里面包含设备编号、经纬度、时间戳串口解析完后就触发public event ActionGpsData GpsDataReceived; private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e) { // 假设这是整行协议以回车结尾 string line serialPort.ReadLine(); GpsData data ParseGpsLine(line); if (data ! null) { GpsDataReceived?.Invoke(data); } }窗体端订阅事件private void OnGpsDataReceived(GpsData data) { if (mapControl.InvokeRequired) { mapControl.Invoke(new ActionGpsData(OnGpsDataReceived), data); return; } PointLatLng point CoordConverter.WGS84ToGCJ02(new PointLatLng(data.Lat, data.Lng)); UpdateDevicePosition(point); }这里我用事件而不是Timer去轮询串口能减少无谓的UI刷新。PLC数据也是同样的套路你通过Modbus或S7协议读到设备的坐标字段封装成一个自定义事件抛出来地图只关心“设备现在在哪”不关心这个位置是谁解析出来的。这样地图模块和通信模块彻底解耦后续换通信方式或加设备类型都不影响地图显示部分。6.2 视觉坐标和地图坐标怎么联动一些项目里还会把视觉检测结果叠加在地图上。比如车间里用HALCON做定位检测检测到某个工件在某个物理坐标有缺陷你想把这个位置标记在车间平面图上。这中间其实经历了好几层坐标变换像素坐标 - 相机坐标系 - 车间物理坐标 - 经纬度坐标。最后一层才是GMap.NET负责的事。如果你做的不是室外GPS而是室内或厂区场景直接经纬度往往不够精确。我的做法是先在车间平面图上标定几个已知经纬度的参考点然后根据设备和参考点的相对位置用线性映射算出当前物理点在经纬度空间里的坐标再喂给地图。这个方法要求你先设定好“底图是卫星图还是平面图”两者混用会导致缩放和旋转都对不上。还有一个和HALCON联动的经验HALCON的DLL如果加载失败比如网上常说的“hoperatorset.queryavailabledldevices失败”通常是本机没有安装对应版本的HALCON运行时或者程序集位数不匹配。这跟GMap.NET本身没什么关系但如果你把视觉模块和地图模块放在同一个进程里两边必须用同一套平台目标。我遇到过项目里GMap.NET以x64跑得很欢偏偏HALCON的授权DLL是x86的一启动就崩。遇到这种问题先统一程序集位数再排查运行时环境不要一上来就怀疑地图控件。如果视觉检测的结果只是用来做“区域标注”比如在图上画一个故障围栏那更好办。直接用GMapPolygon把检测到的区域坐标点连起来填个半透明颜色就能很直观地在总览图上看出来。这种叠加方案比用表格列坐标数据要直观得多给客户演示的时候也更有说服力。我个人在实际操作中还有一个比较受用的习惯会单独封装一个MapHelper类把所有和GMap.NET相关的初始化、坐标转换、图层管理逻辑都放在里面。项目里其他地方只调用Helper的方法不直接碰GMapControl的底层对象。这样做的好处是地图组件将来如果升级或者换图源只需要改Helper这一层业务逻辑完全不用动。最后再分享一个小技巧调试地图显示问题时先看缓存目录里有没有生成瓦片文件如果瓦片文件在说明网络和下载链路是通的问题多半出在坐标或图层显示逻辑上如果瓦片文件都没生成那就是图源访问或缓存配置的问题。这个排查顺序能帮你省下不少时间。本文还有配套的精品资源点击获取
返回列表