第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 的核心架构:

  1. 整体架构:分层设计,核心与平台分离
  2. Map 类:地图容器,管理图层、导航、小部件
  3. Navigator 与 Viewport:视角控制和坐标转换
  4. 渲染系统:基于 SkiaSharp 的统一渲染
  5. 数据获取机制:异步数据加载
  6. 事件系统:统一的指针事件模型
  7. 缓存机制:瓦片缓存和渲染缓存
  8. 线程模型:UI 线程安全
  9. 扩展点:丰富的扩展接口

在下一章中,我们将详细探讨 Map 与 MapControl 的使用方法。

3.11 思考与练习

  1. 解释 Mapsui 分层架构的优势是什么?
  2. Viewport 和 Navigator 的区别是什么?
  3. 为什么 Mapsui 选择 SkiaSharp 作为渲染引擎?
  4. 尝试实现一个简单的自定义 StyleRenderer。
  5. 描述数据获取的完整流程。
posted @ 2026-01-08 14:05  我才是银古  阅读(4)  评论(0)    收藏  举报