LiveCharts2 地图标记点实战:用 GeoVisualElement 在经纬度坐标上绘制城市标记(MarkersOnMap 示例全解析)
数据可视化图表库跨平台【免费下载链接】LiveCharts2Beautiful, interactive charts, maps, and gauges. One API for every .NET UI framework.项目地址https://gitcode.com/gh_mirrors/li/LiveCharts2点击查看免费下载本文以仓库docs/samples/maps/markersOnMap/示例文档为骨架结合其模板渲染链路、ViewModelsSamples中的真实实现、GeoVisualElement与GeoMapChart的底层源码以及快照测试完整讲解如何在 LiveCharts2 的GeoMap上把任意视觉元素标记圆点、气泡、标注等锚定到真实的经度纬度坐标并让标记自动跟随地图的平移、缩放与球体旋转。读完本文你将能复刻官方 MarkersOnMap 示例并掌握Project/Unproject等坐标互转 API为业务地图叠加任意自定义覆盖物。一、示例文档从哪来模板渲染链路仓库中docs/samples/maps/markersOnMap/template.md本身并不是一篇手写的 Markdown而是 LiveCharts2 文档系统使用的 Scriban 模板占位文件其正文只有一行渲染指令{{ render ~/shared/genericSampleSimple.md }}按照 docs/readme.md 的说明docs 目录下的 Markdown 会先经过 Scriban一种类似 Liquid 的模板语言预处理{{ render ... }}会被替换为对应文件的真实内容。这个指令链接着 docs/shared/genericSampleSimple.md后者再依次渲染docs/shared/genericSampleSimpleHeader.md生成文章标题Markers On Map与示例结果图render_current_directory_view_model注入当前示例目录下的 ViewModel 源码即 samples/ViewModelsSamples/Maps/MarkersOnMap/ViewModel.csrender_current_directory_view注入对应平台的视图源码.axaml/.xaml/.razor/ 纯代码docs/shared/relatedTo.md追加相关文章导航。也就是说该示例文档的正文完全来自仓库中可运行的示例代码。因此本文直接以这些真实源码为准——它们既是文档内容也是可以在本仓库直接查看、运行的最小可复现实例。二、示例要解决的问题把标记钉在地图真实坐标上MarkersOnMap 的场景非常典型一张热力底图 若干城市坐标点标记必须锚定在真实经纬度上而不是手工换算的屏幕像素。官方示例横跨东西半球选择了五座城市见 ViewModel.cs城市经度纬度New York纽约-74.0040.71Tokyo东京139.6935.69Madrid马德里-3.7040.42São Paulo圣保罗-46.63-23.55Sydney悉尼151.21-33.87之所以选择这些散布于各半球的城市注释里写得很清楚让标记能可见地跟随平移pan、缩放zoom和正交投影旋转orthographic rotation——如果所有点都挤在一起就看不出标记与地图的联动效果了。示例运行结果如下文档自带的结果图即 docs/samples/maps/markersOnMap/result.png浅灰底色的世界地图上参与热力系列的国土按数值着色五个橙红色圆形标记分别落在美国东部纽约、欧洲西南马德里、巴西东部圣保罗、东亚东京与澳大利亚东南悉尼。三、核心机制GeoVisualElement——经纬度锚点的底层实现标记能钉在经纬度上靠的是LiveChartsCore.Geo命名空间下的 GeoVisualElement.cs。它本质是一个包装器包住一个普通Visual或已过时的VisualElement并暴露Longitude/Latitude两个经纬度属性源码 L77-L81。关键逻辑在它的Invalidate(Chart)重写中源码 L84-L109public override void Invalidate(Chart chart) { if (chart is not GeoMapChart geoMap) throw new InvalidOperationException( ${nameof(GeoVisualElement)} can only be used on a {nameof(GeoMapChart)}.); var pixel geoMap.Project(Longitude, Latitude); if (pixel is null) { // 坐标不在可见区域如正交投影的背面半球时 // 本帧把内部视觉移出画布避免它残留在上一次的屏幕位置。 Visual.RemoveFromUI(chart); return; } SetLocation(pixel.Value.X, pixel.Value.Y); Visual.Invalidate(chart); Visual.RemoveOldPaints(chart.View); }这段源码揭示了三件事也是理解整个示例的钥匙只能用在 GeoMapChart 上构造约束明确GeoVisualElement专属于地图不能用于 Cartesian / Pie 等其他图表每帧重新投影每次 Measure 都会用地图当前投影器把(Longitude, Latitude)换算成屏幕像素因此平移、缩放、球体旋转时标记天然跟随无需手动更新坐标不可见即移除当坐标处于投影不可见区域例如正交投影的背面半球时Project返回null包装器立刻把内部视觉移出画布避免幽灵标记残留在最后一次可见位置。另外还有两处容易被忽略的细节SetLocation对于新式Visual是写visual.DrawnElement.X / Y源码 L124-L127所以内部视觉绝不能在自己的Measure里设置位置否则会覆盖掉投影坐标显式调用Visual.RemoveOldPaints(chart.View)是为了镜像Chart.AddVisual中的旧画笔清理逻辑如果用户运行期把填充色从绿换成红旧画笔不会残留在画布上。四、第一步准备热力底图HeatLandSeries 与短名索引标记要落在地图上首先得有地图。示例的底图用HeatLandSeries实现ViewModel.cspublic HeatLandSeries[] Series { get; } [ new HeatLandSeries { Lands [ new HeatLand { Name usa, Value 15 }, new HeatLand { Name jpn, Value 14 }, new HeatLand { Name esp, Value 10 }, new HeatLand { Name bra, Value 12 }, new HeatLand { Name aus, Value 11 }, ], }, ];HeatLand.Name使用的是国家/地区的短名shortName它与内置世界地图 GeoJSON 的索引文件 docs/_assets/word-map-index.json 一一对应——该文件里可检索到usa、jpn、esp、bra、aus等条目。换句话说给哪个国家赋了值哪个国家就会被热力渐变着色Value则决定颜色深浅。这套映射机制在地图总览文档 docs/geomapchart/overview.md 中有完整说明。五、第二步自定义标记视觉 CityMarkerVisual标记本体是一个继承自Visual的自定义视觉定义在 CityMarkerVisual.cspublic class CityMarkerVisual : Visual { private const float Size 14f; protected override IDrawnElement DrawnElement { get; } new CircleGeometry { Width Size, Height Size, Fill new SolidColorPaint(new SKColor(255, 87, 51)), // OrangeRed Stroke new SolidColorPaint(SKColors.White), StrokeThickness 2, TranslateTransform new(-Size / 2f, -Size / 2f), }; protected override void Measure(Chart chart) { } }这段代码里藏着四个反直觉但必须遵守的要点Measure必须留空投影坐标由GeoVisualElement写入DrawnElement.X / Y如果在这里自己定位就会覆盖锚点坐标标记将不再跟随地图TranslateTransform负责居中投影器给出的是包围盒左上角像素偏移-Size/2后圆形才真正圆心对准经纬度点StrokeThickness必须写在几何体上源码注释明确说明几何体自带 Paint 时以几何体上设置的描边厚度为准写在意料之外的 Paint 上会被静默忽略画出 1px 的细线声明为IDrawnElement而非CircleGeometrynet462不支持协变返回且Measure并不需要派生类型这是出于兼容性的选择。快照测试中使用了另一种等价写法——GeometryVisualCircleGeometry直接内联构造见 tests/SnapshotTests/MapsTests.cs同样是Translate(-size/2f, -size/2f)居中 OrangeRed 填充 白色描边。两种写法效果一致前者便于复用、后者适合一次性声明。六、第三步组装标记并绑定到 GeoMap6.1 工厂方法产出锚定坐标的包装器ViewModel 用一个静态工厂方法把坐标和视觉绑定成GeoVisualElementViewModel.csprivate static GeoVisualElement CityMarker(double longitude, double latitude) new(new CityMarkerVisual()) { Longitude longitude, Latitude latitude, };五座城市各生成一个实例存入IChartElement[] VisualElements属性供视图消费。6.2 各平台视图的绑定方式重要差异值得特别注意的是GeoMap.VisualElements目前并不是所有平台都可直接 XAML 绑定的依赖属性。源码注释见 Avalonia 视图说明GeoMap 的源生成器尚未把它暴露为可绑定的DependencyProperty因此在 XAML 系平台需要退而求其次、在 code-behind 里赋值var vm (ViewModel)DataContext!; var geoMap this.FindGeoMap(geoMap)!; geoMap.VisualElements vm.VisualElements;Avalonia、WPF、.NET MAUI 均采用这一模式而Blazor因为没有依赖属性绑定约束可以在 razor 里直接传参View.razorGeoMap refChart Seriesvm.Series VisualElementsvm.VisualElements MapProjectionLiveChartsCore.Geo.MapProjection.Mercator /GeoMapWinForms / Eto.Forms 则在代码中直接构建控件WinForms 视图、Eto 视图var chart new GeoMap { Series vm.Series, VisualElements vm.VisualElements, MapProjection MapProjection.Mercator, ... };6.3 投影选择Mercator所有平台的示例视图都设置了MapProjectionMercator。投影枚举定义在 MapProjection.cs共三种Default仅把经纬度平移到控件坐标适合非地理用途地图Mercator扁平世界地图保角但极地放大Google Maps / Bing Maps 同款Orthographic3D 球体视图一次只见一个半球可旋转查看另一侧。Mercator 是最直观展示标记跟随地图的投影若换成 Orthographic标记会在球体转到背面时自动消失由第三节的pixel is null分支保证这正是示例注释所说的可见地跟踪旋转。七、底层原理标记如何在每一帧钉住地图把GeoVisualElement放入VisualElements后剩下的工作全部由 GeoMapChart.cs 的 Measure 管线完成注册与清理Measure()先用InitializeVisualsCollector()快照上一帧元素随后遍历MapView.VisualElements里IsVisible的元素逐一AddVisual(visual)源码 L627-L636帧末CollectVisuals()会把仍在待删集合中的元素即从VisualElements移除、或IsVisiblefalse的元素从画布删除。这就是动态增删标记能即时生效的原因投影与视口变换GeoVisualElement.Invalidate调用GeoMapChart.Project(longitude, latitude)源码 L253-L259其内部先检查投影器可见性再经ApplyViewportTransform源码 L279-L286叠加当前ZoomLevel与PanOffset变换——这正是标记能跟随滚轮缩放与拖拽平移的原因旋转球体正交投影下CenterLongitude/CenterLatitude改变视角直接赋值即瞬移RotateTo(longitude, latitude, durationMs 800)则用QuadraticInOut缓动做动画过渡源码 L225-L242且做了经度差归一化——170°→-170° 会走反经线捷径而不是绕地球 340°。动画期间标记随每次投影自动更新位置。八、测试与快照验证仓库用快照测试把 MarkersOnMap 的效果钉死为黄金图像确保后续改动不破坏行为。见 tests/SnapshotTests/MapsTests.csMarkersOnMapL207-L226800×600 的SKGeoMapMercator 投影注入五座城市的GeoVisualElement标记断言渲染结果与MapsTests_MarkersOnMap.png一致MarkersOnMap_RemovedBetweenFramesL306-L332同一图表实例上先渲染两个标记随后把VisualElements置空再渲染一帧断言第二帧画布上标记确实被清除——专门验证上述InitializeVisualsCollector → CollectVisuals生命周期清理路径。快照图就保存在仓库中tests/SnapshotTests/Snapshots/MapsTests_MarkersOnMap.png800×600五个标记同屏九、进阶能力坐标与像素互转、命中检测、交互除了画标记这套机制还派生出一系列实用能力详见 docs/geomapchart/overview.md 的 Custom overlays at lat/lon 小节像素 → 坐标CoreChart.Unproject(LvcPoint)把点击的屏幕像素反算成经纬度返回null表示点击落在投影可见区之外如正交圆盘之外可据此区分点到空白与点到真实坐标坐标 → 像素CoreChart.Project(lon, lat)返回null表示该坐标当前不可见如球体背面可用于自行定位元素或做命中测试同步查询需要即时命中检测例如自定义手势时调用geoMap.GetPointsAt(new LvcPointD(x, y))即可无需订阅事件交互模式InteractionMode控制滚轮缩放与拖拽平移的开关None/Pan/Zoom/Both正交投影默认禁用平移、启用滚轮缩放。若需要完全自定义底图加载自己的 GeoJSON参考 CustomMap 示例 的加载配方若想给标记加上更多形态气泡、图标、标签等通用视觉元素的用法见 VisualElements 示例。赞分享数据可视化图表库跨平台【免费下载链接】LiveCharts2Beautiful, interactive charts, maps, and gauges. One API for every .NET UI framework.项目地址https://gitcode.com/gh_mirrors/li/LiveCharts2点击查看免费下载相关推荐folium MousePosition 插件实战在地图上实时显示鼠标经纬度坐标folium MousePosition 插件实战在地图上实时显示鼠标经纬度坐标 导读 本指南围绕 folium 的 MousePosition 插件展开讲数据可视化数据分析GIS创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

CFSv2季节气候预测系统:耦合模式、数据获取与偏差订正全攻略

CFSv2季节气候预测系统:耦合模式、数据获取与偏差订正全攻略

做季节尺度气候预测的人,几乎没有谁绕得过CFSv2。这套全球耦合气候预测系统,虽然名字听起来像纯科研产品,其实是很多业务化预测服务的底层数据源:ENSO实时监测、未来几个月的气温降水趋势、农业气象服务里的旱涝预判,甚…

2026/10/13 15:56:17 阅读更多 →
COMSOL超表面仿真实战:多极子展开破解透射谱共振之谜

COMSOL超表面仿真实战:多极子展开破解透射谱共振之谜

跟做超表面或者周期性光栅的人聊 COMSOL,最常听到的一句话是:透射谱算出来了,但共振来源说不清楚。我自己的体会很深——谱线在某段波长掉下去一个深谷,S21 从 0.9 掉到 0.15,形态看着像教科书里的共振线型&#xff0c…

2026/10/13 16:30:18 阅读更多 →
Claude Projects与Artifacts实战:打造项目专属大脑与可迭代产出

Claude Projects与Artifacts实战:打造项目专属大脑与可迭代产出

1. 从“聊天窗口”到“项目工作台”的认知转变大多数人第一次接触 Claude 这类对话式 AI 时,习惯把它当成一个更聪明的搜索框:问一句,答一句,关掉页面,下次重新开始。这种用法当然没错,但它只发挥了这类工具…

2026/10/13 14:58:46 阅读更多 →

最新新闻

Cursor额度重置教程:设备指纹清理实现合规续杯

Cursor额度重置教程:设备指纹清理实现合规续杯

简介:本资源是面向软件开发者的 Cursor 编辑器 11 月续杯实战源码包,聚焦解决免费用户模型调用受限、账号续期繁琐等实际痛点,适用于中初级开发者快速提升 AI 编程效率。压缩包共 3 个文件,含 .inscode(Cursor 插件配置…

2026/10/13 19:38:20 阅读更多 →
用Claude Code摸清老项目技术栈:三轮提示词框架与实战记录

用Claude Code摸清老项目技术栈:三轮提示词框架与实战记录

“只管去写”系列到了第二天。今天的任务很明确:用Claude分析一个老项目的技术栈。这件事听起来远没有写一个新功能那么有成就感,但只要你接过大半年之前自己写的代码、接过离职同事留下的半成品项目、或者刚入职需要快速搞清一套业务系统的底细&#xf…

2026/10/13 19:38:20 阅读更多 →
开源协作的隐形基石:高效代码评审实践指南

开源协作的隐形基石:高效代码评审实践指南

做开源项目这几年,我越来越确信一件事:代码评审(code review)是整个协作链条里最被低估的一环。很多人觉得代码写完、测试跑通、合并进去就完事了,可真正把一个项目推过几个版本之后才发现,评审才是让代码质…

2026/10/13 19:38:20 阅读更多 →
Python pickle序列化实战:从协议版本到安全红线全解析

Python pickle序列化实战:从协议版本到安全红线全解析

如果你写过 Python,一定遇到过这类需求:程序跑完把内存里的字典、列表或者某个自定义类实例保存到磁盘,下次启动再原封不动读回来。Python 为此内置了一个序列化模块叫 pickle,标题说得很直白——Pickle 是 Python 内置的序列化模…

2026/10/13 19:38:20 阅读更多 →
AnyPS5:不越狱的PS5增强方案与边界协同架构

AnyPS5:不越狱的PS5增强方案与边界协同架构

项目标题:“AnyPS5”这个名称本身带有强烈的指向性与模糊性并存的特征——它既像一个技术代号,又像一句口号;既暗示兼容性、泛用性(“Any”),又锚定在特定硬件生态(“PS5”)。但问题…

2026/10/13 19:38:20 阅读更多 →
互联网大厂薪酬结构全解析:总包、级别、绩效与谈薪技巧

互联网大厂薪酬结构全解析:总包、级别、绩效与谈薪技巧

1. 先说结论:今年这个薪酬盘子到底什么情况每年这个节点,总有人拿着某头部互联网平台的薪酬截图或者 Offer 在各个群里问“这个数正常吗”。说实话,我在这一行摸爬滚打了十几年,带过团队、做过招聘、也背过绩效,今年这…

2026/10/13 19:37:19 阅读更多 →

日新闻

物联网系统集成避坑指南:从选型到验收的关键要点

物联网系统集成避坑指南:从选型到验收的关键要点

做了这么多年物联网系统集成的项目,我最大的感受是,大部分坑不是出在技术前沿,而是出在一些看起来很平常的地方。设备选了便宜的、协议没问清楚、现场没摸透、验收标准写得含糊——这些细枝末节,最后都会变成延期、超支和扯皮的大…

2026/10/13 0:00:57 阅读更多 →
ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

如果你玩ESP32有一阵子了,很难不碰到舵机控制这道坎。做智能小车要转方向,做机械臂要抬关节,做云台要偏航俯仰,背后都是同一个东西在干活:舵机。而我见过太多人卡在“代码能编译、舵机就是不听话”这个阶段&#xff0c…

2026/10/13 0:00:57 阅读更多 →
CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

1. 为什么CAN总线至今仍是工业现场的“硬通货”?——从汽车底盘到智能温室的真实需求你有没有拆过一辆老款燃油车的中控台?或者调试过某高校实验室里那台跑着Modbus协议却频频丢帧的PLC控制柜?又或者,在某个农业物联网项目里&…

2026/10/13 0:00:57 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/13 2:54:56 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/13 16:22:04 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/13 1:58:08 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 21:05:09 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 18:11:17 阅读更多 →