Mapsui 对 Avalonia 的完整支持,以及 MBTiles 离线瓦片、Raster/Vector 图层、内存位图图层等能力整理而成,全部步骤均可离线完成,无需联网请求在线瓦片。
在 Avalonia 中使用 Mapsui 4.X 和 5.0 绘制圆形时,核心差异集中在 API 设计、渲染逻辑、性能优化 三个层面,5.0 版本对图形绘制进行了重构,更贴合现代 UI 框架的设计理念,同时修复了 4.X 的部分限制。以下是具体差异对比及迁移指南:

一、核心差异总览
| 对比维度 | Mapsui 4.X(Avalonia) | Mapsui 5.0(Avalonia) |
|---|---|---|
| 绘制核心 API | 依赖 MapControl 的 Layers + 自定义 ILayer |
新增 ShapeLayer + 强类型 Circle 几何对象 |
| 坐标处理方式 | 需手动转换屏幕坐标与地理坐标 | 内置地理坐标直接支持,自动投影转换 |
| 样式配置 | 分散在 SymbolStyle/LineStyle 中,配置繁琐 |
统一的 ShapeStyle,支持填充、边框、透明度统一配置 |
| 性能表现 | 大量自定义绘制易引发重绘卡顿 | 优化了渲染管线,支持硬件加速,批量绘制更高效 |
| 灵活性 | 需手动实现圆形算法(如多边形逼近) | 内置高精度圆形渲染,支持半径动态调整 |
| 兼容性 | 兼容 Avalonia 0.10.x,不支持 .NET 6+ 新特性 | 适配 Avalonia 11.x+,支持 .NET 6/7/8,API 更稳定 |
二、开发环境准备
-
安装 .NET 6+ SDK 与 Avalonia MVVM 模板
-
新建 Avalonia 项目后,NuGet 引入 Mapsui.Avalonia(同时自动引入 SkiaSharp 渲染引擎)[10]
-
若需要离线底图,再安装 Mapsui.Extensions(含 MBTiles、Raster 数据源支持)[6]
一、准备离线底图
可选方案(按常用程度排序):
a) MBTiles:提前用 TileMill、Maperitive 或 QGIS 把区域瓦片导出为 .mbtiles 文件(SQLite 单文件),拷贝到项目或用户目录即可。
b) GeoTIFF/GeoPackage:Mapsui 的 RasterSource 可直接读取本地影像,适合小范围高精度底图。
c) 自定义文件夹瓦片:将 XYZ 结构文件夹放在本地磁盘,通过 LocalFileTileProvider 加载。
路径只需应用有读取权限,部署时随行携带即可,实现真 · 离线。
三、初始化 Mapsui 控件与离线层
-
在窗口或用户控件中置入
<mapsui:MapControl Name="mapCtl"/>。 -
代码端新建
Map实例,先添加离线底图层:-- MBTiles 层:
new TileLayer(new MBTilesSource("Data/mymap.mbtiles", 0, 14))-- 影像层:
new RasterLayer(new RasterSource("Data/area.tif")) -
设置
mapCtl.Map = map;完成底图加载。
cs
private async Task InitializeMap()
{
// 获取MapControl实例
var mapControl = this.FindControl<MapControl>("map");
if (mapControl != null)
{
// 创建地图
try
{
await Task.Run(() =>
{
var connectionString = new SQLite.SQLiteConnectionString("E:\\MySolution\\UAV\\create_mbtiles\\roadmap.mbtiles", false); // 替换为MBTiles文件路径
var tileSource = new BruTile.MbTiles.MbTilesTileSource(connectionString);
// 创建图层并添加到地图
var layer = new TileLayer(tileSource)
{
Name = "Offline Map",
Enabled = true, // 确保图层启用,
};
var map = new Map();
//var map = new Map
//{
// CRS = "EPSG:3857", // 设置地图的坐标系统为 Web Mercator
//};
map.Layers.Add(layer);
mapControl.Map = map;
});
//await Task.Delay(1000);
await Dispatcher.UIThread.InvokeAsync(() =>
{
zoom = 13;
// 转换经纬度到地图的坐标系统
var point = new MPoint(87.62444, 43.830763); // 经度在前,纬度在后
var transformedPoint = SphericalMercator.FromLonLat(point.X, point.Y);
mapControl.Map.Navigator.CenterOn(transformedPoint.x, transformedPoint.y);
mapControl.Map.Navigator.ZoomToLevel(zoom);
//mapControl.Map.Navigator.ZoomTo(zoom); // 设置合适的缩放级别
TextTitle1.Text = string.Format("{0}-{1}", mapControl.Map.Navigator.Viewport.Resolution, mapControl.Map.Navigator.Viewport.Rotation);
TextTitle.Text = zoom.ToString();
// //SetMapViewToUrumqi(mapControl);
// //AddMarkersToMap(mapControl);
// //SetupMapInteractions(mapControl);
mapControl.Refresh();
});
}
catch (System.Exception ex)
{
Console.WriteLine($"设置地图视图失败: {ex.Message}");
}
}
}
-
生成"信号扩散"位图
目标是把计算后的场强栅格变成一张带透明通道的 PNG,再叠加到地图。
-
在内存建
WriteableBitmap(Avalonia 位图)或SKBitmap(Skia 原生),分辨率可同可视区域,也可预生成 512/1024 等二次方尺寸。 -
按经纬度→像素坐标转换(用 Mapsui 的
SphericalMercator类即可)后,对每个像素采样距离、衰减模型,算出 dBm→颜色梯度,写入 RGBA。 -
完成后得到一张透明底 PNG,中心强、边缘透明,扩散效果完成。
-
-
把位图作为图层叠加
Mapsui 允许将任意
SKBitmap包装成MemoryLayer或ImageLayer:-
创建
RasterLayer(new RasterSource(bitmap, 覆盖范围Envelope)) -
将该层插入到
map.Layers.Insert(1, signalLayer),处于底图之上、注记之下。 -
若后续需要移动/更新,只需重新生成位图并替换该层,调用
RefreshGraphics即可实时刷新。
-
cs
/// <summary>
/// 圆扩散效果
/// </summary>
/// <returns></returns>
private async Task AddCircle()
{
// 扩散效果参数
var minScale = 1.0; // 最小缩放值
var maxScale = 20; // 最大缩放值(扩散范围)
var scaleStep = 0.5; // 每次递增的缩放步长
var alphaStep = 5; // 透明度递减步长
var currentScale = minScale;
var currentAlpha = 100; // 初始透明度(0-255)
// 创建用于绘制图形的内存图层
var graphicsLayer = new MemoryLayer("GraphicsLayer");
var mapControl = this.FindControl<MapControl>("map")?.Map;
mapControl?.Layers.Add(graphicsLayer); // 添加到地图
var point = new MPoint(87.62444, 43.830763); // 经度在前,纬度在后
var transformedPoint = SphericalMercator.FromLonLat(point.X, point.Y);
// 添加圆形
var circleFeature = new PointFeature(transformedPoint.x, transformedPoint.y); // 圆形中心坐标(x,y)
var pulseStyle = new SymbolStyle
{
SymbolType = SymbolType.Ellipse,
Outline = new Pen(Color.Red, 2), // 红色边框
Fill = new Brush(Color.FromArgb(currentAlpha, 255, 0, 0)), // 红色填充
SymbolScale = 10 // 初始大小
};
circleFeature.Styles.Add(pulseStyle);
//这个值一定不能放在前面,否则会出错
SymbolStyle.DefaultWidth = 20; // 覆盖默认宽度
SymbolStyle.DefaultHeight = 20; // 覆盖默认高度
// 2. 上层:图片(通过缩放和偏移与圆形对齐)
var imageStyle = new ImageStyle
{
Image = new Mapsui.Styles.Image
{
Source = "file://E:/MySolution/UAV/UAVSolution/UAVMonitor/Assets/images/disturb.png"
},
SymbolScale = 1.0, // 图片缩放(确保适配圆形大小)
//RelativeOffset = new RelativeOffset(1, 1) // 图片中心与圆形中心对齐
};
circleFeature.Styles.Add(imageStyle);
// 创建要素并添加样式(先添加圆形,再添加图片,确保图片在上方)
//var combinedFeature = new PointFeature(transformedPoint.x, transformedPoint.y);
//combinedFeature.Styles.Add(pulseStyle); // 先添加圆形(底层)
//combinedFeature.Styles.Add(imageStyle); // 后添加图片(上层)
// 将图形添加到图层
graphicsLayer.Features = new List<Mapsui.Layers.PointFeature> { circleFeature };
graphicsLayer.FeaturesWereModified(); // 通知图层数据已更新
// 创建定时器(每50ms更新一次)
var timer = new Timer(50);
timer.Elapsed += (sender, e) =>
{
// 跨线程更新UI(如果是WPF/WinForms需要Dispatcher)
Dispatcher.UIThread.InvokeAsync(() =>
{
// 更新缩放值
currentScale += scaleStep;
// 更新透明度(随扩散逐渐变淡)
currentAlpha -= alphaStep;
// 超出范围后重置
if (currentScale > maxScale || currentAlpha <= 0)
{
currentScale = minScale;
currentAlpha = 100;
}
// 更新样式的SymbolScale和透明度
pulseStyle.SymbolScale = currentScale;
pulseStyle.Fill.Color = Color.FromArgb(currentAlpha, 255, 0, 0);
pulseStyle.Outline.Color = Color.FromArgb(currentAlpha, 255, 0, 0);
// 通知图层数据变更并刷新地图
graphicsLayer.FeaturesWereModified();
mapControl?.Refresh();
});
};
// 启动定时器
timer.Start();
}
-
坐标与范围同步
-
扩散图需要知道"世界范围"------在生成位图时记录左下、右上经纬度,构造
Envelope赋给 RasterSource,Mapsui 会自动缩放/平移到正确位置。 -
当用户平移缩放地图时,无需重绘位图,只要范围不变就不必重新生成;若信号源或强度变化,则后台重新渲染并替换图层。
-
-
性能与体验优化
-
大区域高分辨率栅格可先裁切到可视区大小,减少像素运算。
-
多个信号源可合并到同一张位图,减少图层数量。
-
若需要动画"雷达扩散",可定时(200 ms)生成递增半径的位图并替换,利用 Skia 的 GPU 加速渲染依旧流畅。
-
对底图做层级缓存(Mapsui 默认已做),缩放时先显示低清瓦片,停止后再清析高清瓦片,避免卡顿。
-
-
部署与打包
-
MBTiles/影像/程序集一并发布,路径可用
AppContext.BaseDirectory拼接,确保桌面、移动端都能定位。 -
Mapsui 基于 SkiaSharp,在 Windows、Linux、macOS、Android、iOS 均可直接运行,无需额外依赖。
-