第03章 - 核心架构与组件设计
第03章:核心架构与组件设计
3.1 Mapsui 整体架构
3.1.1 架构概述
Mapsui 采用分层架构设计,将核心功能与平台特定实现分离,实现了高度的可移植性和可扩展性:
┌─────────────────────────────────────────────────────────────────┐
│ UI 框架层 │
│ WPF │ MAUI │ Avalonia │ Blazor │ WinUI │ Forms │ ... │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Mapsui.UI.* 适配层 │
│ MapControl 的各平台实现,处理平台特定的输入和渲染 │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Mapsui.Rendering.Skia │
│ 基于 SkiaSharp 的统一渲染层 │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Mapsui 核心层 │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ Map │ │ Layer │ │ Style │ │ Widget │ │
│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │Navigator│ │Provider │ │ Feature │ │Viewport │ │
│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 扩展层 │
│ Mapsui.Nts │ Mapsui.Tiling │ Mapsui.ArcGIS │ Mapsui.Extensions│
└─────────────────────────────────────────────────────────────────┘
3.1.2 核心组件关系
┌──────────────┐
│ MapControl │
│ (UI 控件) │
└──────┬───────┘
│
┌──────▼───────┐
│ Map │
│ (地图容器) │
└──────┬───────┘
┌───────────────────┼───────────────────┐
│ │ │
┌──────▼──────┐ ┌──────▼──────┐ ┌──────▼──────┐
│ Layers │ │ Navigator │ │ Widgets │
│ (图层集合) │ │ (导航器) │ │ (小部件集合) │
└──────┬──────┘ └──────┬──────┘ └─────────────┘
│ │
┌──────▼──────┐ ┌──────▼──────┐
│ Layer │ │ Viewport │
│ (图层) │ │ (视口) │
└──────┬──────┘ └─────────────┘
│
┌──────▼──────┐
│ Provider │
│ (数据提供者) │
└──────┬──────┘
│
┌──────▼──────┐
│ Features │
│ (要素) │
└─────────────┘
3.2 Map 类详解
3.2.1 Map 类的职责
Map 类是 Mapsui 的核心容器,负责管理地图的所有组件:
public class Map : INotifyPropertyChanged, IDisposable
{
// 图层管理
public LayerCollection Layers { get; }
// 导航控制
public Navigator Navigator { get; }
// 小部件管理
public IList<IWidget> Widgets { get; }
// 坐标参考系统
public string CRS { get; set; }
// 背景颜色
public Color BackColor { get; set; }
// 事件
public event EventHandler<MapInfoEventArgs>? Info;
public event EventHandler<DataChangedEventArgs>? DataChanged;
public event PropertyChangedEventHandler? PropertyChanged;
}
3.2.2 Map 的核心方法
// 创建和配置 Map
var map = new Map
{
CRS = "EPSG:3857",
BackColor = Color.White
};
// 图层操作
map.Layers.Add(layer);
map.Layers.Remove(layer);
map.Layers.Move(fromIndex, toIndex);
map.Layers.Clear();
// 访问特定图层
var layer = map.Layers.FindLayer("LayerName");
var allLayers = map.Layers.ToList();
// 刷新地图
map.Refresh();
map.RefreshGraphics();
map.RefreshData();
// 获取地图信息
var mapInfo = map.GetMapInfo(screenPosition, viewport);
3.2.3 Map 事件处理
// 地图信息事件(点击查询)
map.Info += (sender, args) =>
{
if (args.MapInfo?.Feature != null)
{
Console.WriteLine($"Clicked feature: {args.MapInfo.Feature}");
}
};
// 数据变化事件
map.DataChanged += (sender, args) =>
{
Console.WriteLine($"Data changed: {args.Error}");
};
// 属性变化事件
map.PropertyChanged += (sender, args) =>
{
Console.WriteLine($"Property changed: {args.PropertyName}");
};
3.3 Navigator 与 Viewport
3.3.1 Viewport(视口)
Viewport 定义了当前可见的地图区域和变换参数:
public class Viewport
{
// 中心点坐标(地图坐标系)
public double CenterX { get; }
public double CenterY { get; }
// 分辨率(每像素代表的地图单位)
public double Resolution { get; }
// 旋转角度(弧度)
public double Rotation { get; }
// 视口尺寸(像素)
public double Width { get; }
public double Height { get; }
// 可见范围
public MRect Extent { get; }
// 坐标转换方法
public MPoint ScreenToWorld(MPoint screenPosition);
public MPoint WorldToScreen(MPoint worldPosition);
}
3.3.2 Navigator(导航器)
Navigator 提供了控制地图视角的方法:
public class Navigator
{
// 当前视口
public Viewport Viewport { get; }
// 缩放限制
public MMinMax ZoomLimits { get; set; }
public MRect? PanLimits { get; set; }
// 缩放方法
public void ZoomIn();
public void ZoomOut();
public void ZoomTo(double resolution);
public void ZoomToBox(MRect extent, MBoxFit boxFit = MBoxFit.Fit);
// 平移方法
public void CenterOn(double x, double y);
public void CenterOn(MPoint center);
public void NavigateTo(MRect extent);
// 旋转方法
public void RotateTo(double rotation);
// 动画导航
public void CenterOnAndZoomTo(MPoint center, double resolution, long duration = 0);
public void FlyTo(MRect extent, long duration = 500);
}
3.3.3 导航示例
// 缩放到特定区域
var china = new MRect(
SphericalMercator.FromLonLat(73, 18).X,
SphericalMercator.FromLonLat(73, 18).Y,
SphericalMercator.FromLonLat(135, 54).X,
SphericalMercator.FromLonLat(135, 54).Y
);
map.Navigator.ZoomToBox(china);
// 定位到特定点
var beijing = SphericalMercator.FromLonLat(116.4, 39.9);
map.Navigator.CenterOn(beijing.X, beijing.Y);
// 带动画的导航
map.Navigator.FlyTo(china, duration: 1000);
// 设置缩放限制
map.Navigator.ZoomLimits = new MMinMax(100, 100000000);
// 设置平移限制(限制在中国范围内)
map.Navigator.PanLimits = china;
3.4 渲染系统
3.4.1 渲染架构
Mapsui 使用 SkiaSharp 作为渲染引擎:
┌─────────────────────────────────────────────────────────────────┐
│ MapRenderer │
│ (地图渲染器 - 入口点) │
└─────────────────────────────────────────────────────────────────┘
│
┌─────────────────────┼─────────────────────┐
│ │ │
┌────▼────┐ ┌────▼────┐ ┌────▼────┐
│ Layer │ │ Widget │ │ Style │
│Renderer │ │Renderer │ │Renderer │
└────┬────┘ └────┬────┘ └────┬────┘
│ │ │
└─────────────────────┼─────────────────────┘
│
┌──────▼──────┐
│ SkiaSharp │
│ SKCanvas │
└─────────────┘
3.4.2 自定义渲染器
可以通过实现 IStyleRenderer 接口创建自定义渲染器:
public class CustomStyleRenderer : IStyleRenderer
{
public void Draw(SKCanvas canvas, Viewport viewport, ILayer layer,
IFeature feature, IStyle style, IRenderCache renderCache,
long iteration)
{
if (style is CustomStyle customStyle && feature is PointFeature pointFeature)
{
var screenPoint = viewport.WorldToScreen(pointFeature.Point);
using var paint = new SKPaint
{
Color = customStyle.Color.ToSkia(),
Style = SKPaintStyle.Fill,
IsAntialias = true
};
canvas.DrawCircle(
(float)screenPoint.X,
(float)screenPoint.Y,
customStyle.Radius,
paint
);
}
}
}
3.4.3 注册自定义渲染器
// 在应用启动时注册
MapRenderer.DefaultRenderers[typeof(CustomStyle)] = new CustomStyleRenderer();
3.5 数据获取机制
3.5.1 异步数据获取
Mapsui 使用异步机制获取数据,特别是对于瓦片图层:
public interface IAsyncDataFetcher
{
void DataChanged(object sender, DataChangedEventArgs e);
void AbortFetch();
}
3.5.2 数据获取流程
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Viewport │────▶│ FetchInfo │────▶│ Provider │
│ Changed │ │ Created │ │ Fetch │
└─────────────┘ └─────────────┘ └──────┬──────┘
│
▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Map │◀────│ Layer │◀────│ Features │
│ Refresh │ │ Updated │ │ Returned │
└─────────────┘ └─────────────┘ └─────────────┘
3.5.3 FetchInfo 类
public class FetchInfo
{
// 请求的范围
public MRect Extent { get; }
// 请求的分辨率
public double Resolution { get; }
// 坐标参考系统
public string CRS { get; }
// 变化类型
public ChangeType ChangeType { get; }
}
3.6 事件系统
3.6.1 V5 版本的新事件模型
Mapsui V5 引入了统一的指针事件模型:
// 指针事件类型
public enum PointerEventType
{
PointerPressed, // 按下
PointerMoved, // 移动
PointerReleased, // 释放
Tapped // 点击(单击、双击、长按)
}
// 事件参数
public class WidgetEventArgs : EventArgs
{
public MPoint ScreenPosition { get; }
public int NumTaps { get; } // 点击次数
public bool Handled { get; set; } // 是否已处理
// 获取地图信息
public MapInfo? GetMapInfo(Map map, Viewport viewport);
}
3.6.2 处理地图事件
// 在 MapControl 中处理事件
mapControl.Map.Info += (sender, args) =>
{
var mapInfo = args.MapInfo;
if (mapInfo?.Feature != null)
{
// 处理要素点击
HandleFeatureClick(mapInfo.Feature);
}
else if (mapInfo?.WorldPosition != null)
{
// 处理空白区域点击
HandleMapClick(mapInfo.WorldPosition);
}
};
3.7 缓存机制
3.7.1 瓦片缓存
Mapsui 通过 BruTile 库提供瓦片缓存支持:
// 内存缓存
var memoryCache = new MemoryCache<byte[]>();
// 文件缓存
var fileCache = new FileCache(
directory: "cache/tiles",
format: "png"
);
// 使用缓存创建瓦片源
var tileSource = new HttpTileSource(
new GlobalSphericalMercator(),
"https://tile.openstreetmap.org/{z}/{x}/{y}.png",
persistentCache: fileCache
);
3.7.2 渲染缓存
Mapsui 内部维护渲染缓存以提高性能:
public interface IRenderCache
{
T GetOrCreate<T>(object key, Func<T> create) where T : class;
void Clear();
}
3.8 线程模型
3.8.1 UI 线程与后台线程
Mapsui 遵循 UI 线程安全原则:
// 在 UI 线程上操作 Map
Dispatcher.Invoke(() =>
{
map.Layers.Add(newLayer);
map.Refresh();
});
// 数据获取在后台线程
Task.Run(() =>
{
var features = FetchFeaturesFromDatabase();
// 切换回 UI 线程更新图层
Dispatcher.Invoke(() =>
{
memoryLayer.Features = features;
memoryLayer.DataHasChanged();
});
});
3.8.2 异步操作最佳实践
public async Task LoadDataAsync()
{
// 后台获取数据
var data = await Task.Run(() =>
{
return LoadGeoJsonFromFile("data.geojson");
});
// 在 UI 线程更新
var features = ConvertToFeatures(data);
_dataLayer.Clear();
foreach (var feature in features)
{
_dataLayer.Add(feature);
}
_dataLayer.DataHasChanged();
}
3.9 扩展点
3.9.1 可扩展组件
| 组件 | 扩展方式 | 用途 |
|---|---|---|
| Layer | 继承 BaseLayer | 自定义图层类型 |
| Provider | 实现 IProvider | 自定义数据源 |
| Style | 继承 BaseStyle | 自定义样式类型 |
| StyleRenderer | 实现 IStyleRenderer | 自定义渲染逻辑 |
| Widget | 继承 BaseWidget | 自定义 UI 控件 |
| WidgetRenderer | 实现 IWidgetRenderer | 自定义小部件渲染 |
3.9.2 扩展示例:自定义图层
public class HeatmapLayer : BaseLayer
{
private readonly IEnumerable<MPoint> _points;
private readonly double _radius;
public HeatmapLayer(IEnumerable<MPoint> points, double radius = 50)
{
_points = points;
_radius = radius;
Name = "Heatmap";
}
public override IEnumerable<IFeature> GetFeatures(MRect extent, double resolution)
{
// 返回在视口范围内的点
return _points
.Where(p => extent.Contains(p))
.Select(p => new PointFeature(p));
}
public override MRect? Extent => GetExtentFromPoints(_points);
}
3.10 本章小结
本章深入介绍了 Mapsui 的核心架构:
- 整体架构:分层设计,核心与平台分离
- Map 类:地图容器,管理图层、导航、小部件
- Navigator 与 Viewport:视角控制和坐标转换
- 渲染系统:基于 SkiaSharp 的统一渲染
- 数据获取机制:异步数据加载
- 事件系统:统一的指针事件模型
- 缓存机制:瓦片缓存和渲染缓存
- 线程模型:UI 线程安全
- 扩展点:丰富的扩展接口
在下一章中,我们将详细探讨 Map 与 MapControl 的使用方法。
3.11 思考与练习
- 解释 Mapsui 分层架构的优势是什么?
- Viewport 和 Navigator 的区别是什么?
- 为什么 Mapsui 选择 SkiaSharp 作为渲染引擎?
- 尝试实现一个简单的自定义 StyleRenderer。
- 描述数据获取的完整流程。

浙公网安备 33010602011771号