第05章 - 图层系统详解

第05章:图层系统详解

5.1 图层概述

5.1.1 图层的作用

图层(Layer)是 Mapsui 中组织和管理地图内容的基本单位。每个图层代表一类地理信息,多个图层叠加形成完整的地图视图:

┌─────────────────────────────────────────────────────────────────┐
│                        地图视图                                  │
├─────────────────────────────────────────────────────────────────┤
│  ┌─────────────────────────────────────────────────────────┐   │
│  │                  标注图层 (最上层)                        │   │
│  └─────────────────────────────────────────────────────────┘   │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │                  POI 点图层                               │   │
│  └─────────────────────────────────────────────────────────┘   │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │                  道路线图层                               │   │
│  └─────────────────────────────────────────────────────────┘   │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │                  建筑面图层                               │   │
│  └─────────────────────────────────────────────────────────┘   │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │                  底图瓦片图层 (最下层)                    │   │
│  └─────────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────────┘

5.1.2 图层类型体系

                        ILayer (接口)
                           │
                      BaseLayer (基类)
                           │
           ┌───────────────┼───────────────┐
           │               │               │
     MemoryLayer      ImageLayer        Layer
           │                               │
    WritableLayer                    TileLayer
           │
  ObservableMemoryLayer

5.1.3 ILayer 接口

public interface ILayer : IDisposable
{
    // 标识
    string Name { get; set; }
    long Id { get; }
    
    // 状态
    bool Enabled { get; set; }        // 是否启用
    double Opacity { get; set; }       // 不透明度 (0-1)
    bool Busy { get; }                 // 是否正在获取数据
    
    // 样式
    IStyle? Style { get; set; }
    
    // 范围
    MRect? Extent { get; }
    
    // 缩放限制
    double MinVisible { get; set; }   // 最小可见分辨率
    double MaxVisible { get; set; }   // 最大可见分辨率
    
    // 数据获取
    IEnumerable<IFeature> GetFeatures(MRect extent, double resolution);
    
    // 事件
    event DataChangedEventHandler? DataChanged;
}

5.2 MemoryLayer - 内存图层

5.2.1 基本使用

MemoryLayer 是最常用的图层类型,用于存储和显示内存中的要素:

// 创建 MemoryLayer
var memoryLayer = new MemoryLayer
{
    Name = "Points",
    Features = CreateFeatures(),
    Style = new SymbolStyle
    {
        SymbolScale = 0.5,
        Fill = new Brush(Color.Red)
    }
};

// 添加到地图
map.Layers.Add(memoryLayer);

// 创建要素的方法
private IEnumerable<IFeature> CreateFeatures()
{
    var features = new List<PointFeature>();
    
    var cities = new[]
    {
        (Name: "北京", Lon: 116.4, Lat: 39.9),
        (Name: "上海", Lon: 121.5, Lat: 31.2),
        (Name: "广州", Lon: 113.3, Lat: 23.1)
    };
    
    foreach (var city in cities)
    {
        var point = SphericalMercator.FromLonLat(city.Lon, city.Lat).ToMPoint();
        var feature = new PointFeature(point);
        feature["name"] = city.Name;
        features.Add(feature);
    }
    
    return features;
}

5.2.2 动态更新数据

// 更新要素数据
var layer = map.Layers.FindLayer("Points") as MemoryLayer;
if (layer != null)
{
    // 方式1:替换整个要素集合
    layer.Features = newFeatures;
    layer.DataHasChanged();
    
    // 方式2:使用要素的可枚举属性(推荐用于大数据集)
    layer.Features = GetFeaturesFromDatabase();
    layer.DataHasChanged();
}

5.2.3 按需加载数据

// 创建延迟加载的 MemoryLayer
var lazyLayer = new MemoryLayer
{
    Name = "LazyData",
    Features = new LazyFeatureCollection(() => LoadFeaturesFromDatabase()),
    Style = CreateStyle()
};

// 自定义延迟加载集合
public class LazyFeatureCollection : IEnumerable<IFeature>
{
    private readonly Func<IEnumerable<IFeature>> _loader;
    private IEnumerable<IFeature>? _cache;
    
    public LazyFeatureCollection(Func<IEnumerable<IFeature>> loader)
    {
        _loader = loader;
    }
    
    public IEnumerator<IFeature> GetEnumerator()
    {
        _cache ??= _loader().ToList();
        return _cache.GetEnumerator();
    }
    
    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

5.3 WritableLayer - 可写图层

5.3.1 基本使用

WritableLayer 继承自 MemoryLayer,提供了添加、删除、清除要素的方法:

// 创建 WritableLayer
var writableLayer = new WritableLayer
{
    Name = "EditablePoints",
    Style = new SymbolStyle
    {
        SymbolScale = 0.5,
        Fill = new Brush(Color.Blue)
    }
};

// 添加要素
var point = SphericalMercator.FromLonLat(116.4, 39.9).ToMPoint();
var feature = new PointFeature(point);
feature["name"] = "新增点";

writableLayer.Add(feature);
writableLayer.DataHasChanged();

// 批量添加
writableLayer.AddRange(featureList);
writableLayer.DataHasChanged();

// 删除要素
writableLayer.TryRemove(feature);
writableLayer.DataHasChanged();

// 清除所有要素
writableLayer.Clear();
writableLayer.DataHasChanged();

5.3.2 与用户交互结合

public class DrawingManager
{
    private readonly WritableLayer _drawLayer;
    private readonly Map _map;
    
    public DrawingManager(Map map)
    {
        _map = map;
        _drawLayer = new WritableLayer
        {
            Name = "Drawing",
            Style = CreateDrawingStyle()
        };
        _map.Layers.Add(_drawLayer);
    }
    
    public void AddPointAtClick(MPoint worldPosition)
    {
        var feature = new PointFeature(worldPosition);
        feature["created"] = DateTime.Now;
        
        _drawLayer.Add(feature);
        _drawLayer.DataHasChanged();
    }
    
    public void DeleteFeature(IFeature feature)
    {
        _drawLayer.TryRemove(feature);
        _drawLayer.DataHasChanged();
    }
    
    public void ClearAll()
    {
        _drawLayer.Clear();
        _drawLayer.DataHasChanged();
    }
    
    public IEnumerable<IFeature> GetAllFeatures()
    {
        return _drawLayer.GetFeatures(null, 0);
    }
    
    private IStyle CreateDrawingStyle()
    {
        return new SymbolStyle
        {
            SymbolScale = 0.6,
            Fill = new Brush(Color.Orange),
            Outline = new Pen(Color.Black, 2)
        };
    }
}

5.4 Layer - 基于数据提供者的图层

5.4.1 Layer 类的特点

Layer 类使用数据提供者(Provider)来获取数据,支持异步数据获取:

// 创建基于 Provider 的 Layer
var layer = new Layer
{
    Name = "WfsLayer",
    DataSource = new WfsProvider(new Uri("https://example.com/wfs")),
    Style = new VectorStyle
    {
        Fill = new Brush(Color.Blue),
        Line = new Pen(Color.Red, 1)
    }
};

// 配置数据获取
layer.DataSource.CRS = "EPSG:4326";  // 数据源坐标系

5.4.2 自定义 Provider

public class DatabaseProvider : IProvider
{
    private readonly string _connectionString;
    private readonly string _tableName;
    
    public string? CRS { get; set; } = "EPSG:4326";
    public MRect? Extent { get; private set; }
    
    public DatabaseProvider(string connectionString, string tableName)
    {
        _connectionString = connectionString;
        _tableName = tableName;
    }
    
    public IEnumerable<IFeature> GetFeatures(FetchInfo fetchInfo)
    {
        var features = new List<IFeature>();
        
        using var connection = new NpgsqlConnection(_connectionString);
        connection.Open();
        
        var extent = fetchInfo.Extent;
        var sql = $@"
            SELECT id, name, ST_AsBinary(geom) as geom 
            FROM {_tableName}
            WHERE geom && ST_MakeEnvelope({extent.MinX}, {extent.MinY}, 
                                          {extent.MaxX}, {extent.MaxY}, 4326)";
        
        using var cmd = new NpgsqlCommand(sql, connection);
        using var reader = cmd.ExecuteReader();
        
        while (reader.Read())
        {
            var wkb = (byte[])reader["geom"];
            var geometry = new WKBReader().Read(wkb);
            
            var feature = new GeometryFeature(geometry);
            feature["id"] = reader["id"];
            feature["name"] = reader["name"];
            
            features.Add(feature);
        }
        
        return features;
    }
}

5.5 TileLayer - 瓦片图层

5.5.1 使用在线瓦片服务

// OpenStreetMap
var osmLayer = OpenStreetMap.CreateTileLayer();
map.Layers.Add(osmLayer);

// 自定义瓦片源
var customTileLayer = new TileLayer(
    new HttpTileSource(
        new GlobalSphericalMercator(),
        "https://tiles.example.com/{z}/{x}/{y}.png",
        name: "CustomTiles"
    )
);
map.Layers.Add(customTileLayer);

5.5.2 使用离线瓦片 (MBTiles)

// 使用 BruTile.MBTiles
var mbTilesPath = "path/to/tiles.mbtiles";
var mbTilesTileSource = new MbTilesTileSource(
    new SQLiteConnectionString(mbTilesPath, false)
);

var offlineLayer = new TileLayer(mbTilesTileSource)
{
    Name = "OfflineMap"
};
map.Layers.Add(offlineLayer);

5.5.3 配置瓦片缓存

// 配置文件缓存
var cacheDir = Path.Combine(
    Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
    "MapsuiCache"
);

var tileSource = new HttpTileSource(
    new GlobalSphericalMercator(),
    "https://tile.openstreetmap.org/{z}/{x}/{y}.png",
    name: "OSM",
    persistentCache: new FileCache(cacheDir, "png")
);

var cachedLayer = new TileLayer(tileSource);

5.6 ImageLayer - 图片图层

5.6.1 显示单张图片

// 创建图片图层
var imageLayer = new ImageLayer("Overlay")
{
    DataSource = new ImageProvider(
        imageBytes,
        new MRect(minX, minY, maxX, maxY)  // 图片在地图上的范围
    )
};

map.Layers.Add(imageLayer);

5.6.2 从文件加载图片

// 从文件加载
var imagePath = "path/to/overlay.png";
var imageBytes = File.ReadAllBytes(imagePath);

// 定义图片的地理范围(Web Mercator 坐标)
var extent = new MRect(
    SphericalMercator.FromLonLat(116.0, 39.0).X,
    SphericalMercator.FromLonLat(116.0, 39.0).Y,
    SphericalMercator.FromLonLat(117.0, 40.0).X,
    SphericalMercator.FromLonLat(117.0, 40.0).Y
);

var imageLayer = new ImageLayer("OverlayImage")
{
    DataSource = new ImageProvider(imageBytes, extent),
    Opacity = 0.7  // 设置透明度
};

5.7 RasterizingLayer - 栅格化图层

5.7.1 提高大数据量图层的性能

当矢量图层包含大量要素时,可以使用 RasterizingLayer 将其栅格化以提高渲染性能:

// 创建一个包含大量要素的原始图层
var originalLayer = new MemoryLayer
{
    Name = "OriginalData",
    Features = CreateLargeFeatureSet()  // 假设有 10000+ 要素
};

// 包装为 RasterizingLayer
var rasterizedLayer = new RasterizingLayer(originalLayer)
{
    Name = "RasterizedData"
};

map.Layers.Add(rasterizedLayer);

5.7.2 配置栅格化参数

var rasterizingLayer = new RasterizingLayer(
    originalLayer,
    renderTimeout: 1000,      // 渲染超时(毫秒)
    overscanRatio: 1.0        // 过扫描比例
);

5.8 MyLocationLayer - 位置图层

5.8.1 显示用户位置

// 创建位置图层
var myLocationLayer = new MyLocationLayer(map)
{
    Name = "MyLocation"
};

map.Layers.Add(myLocationLayer);

// 更新位置
myLocationLayer.UpdateMyLocation(
    new MPoint(
        SphericalMercator.FromLonLat(116.4, 39.9).X,
        SphericalMercator.FromLonLat(116.4, 39.9).Y
    ),
    true  // 是否动画移动
);

// 更新方向
myLocationLayer.UpdateMyDirection(45);  // 角度

5.8.2 自定义位置样式

// MyLocationLayer 使用内置样式
// 如需完全自定义,可以创建自己的位置图层
public class CustomLocationLayer : MemoryLayer
{
    private PointFeature _locationFeature;
    
    public CustomLocationLayer()
    {
        Name = "CustomLocation";
        Style = CreateLocationStyle();
    }
    
    public void UpdateLocation(double lon, double lat)
    {
        var point = SphericalMercator.FromLonLat(lon, lat).ToMPoint();
        _locationFeature = new PointFeature(point);
        
        Features = new[] { _locationFeature };
        DataHasChanged();
    }
    
    private IStyle CreateLocationStyle()
    {
        return new SymbolStyle
        {
            SymbolScale = 0.8,
            Fill = new Brush(Color.Blue),
            Outline = new Pen(Color.White, 3)
        };
    }
}

5.9 图层集合管理

5.9.1 LayerCollection 操作

// 添加图层
map.Layers.Add(layer);
map.Layers.Insert(0, baseLayer);  // 插入到指定位置

// 移除图层
map.Layers.Remove(layer);
map.Layers.RemoveAt(0);

// 查找图层
var layer = map.Layers.FindLayer("LayerName");
var allLayers = map.Layers.ToList();

// 调整顺序
map.Layers.Move(0, 2);  // 将索引0的图层移动到索引2

// 清空
map.Layers.Clear();

5.9.2 图层可见性控制

// 控制图层可见性
layer.Enabled = true;   // 显示
layer.Enabled = false;  // 隐藏

// 基于缩放级别的可见性
layer.MinVisible = 500;      // 分辨率小于500时可见(放大时)
layer.MaxVisible = 50000;    // 分辨率大于50000时不可见(缩小时)

// 设置透明度
layer.Opacity = 0.5;  // 50% 透明

5.9.3 图层事件

// 监听图层集合变化
map.Layers.CollectionChanged += (sender, args) =>
{
    Console.WriteLine($"Layers changed: {args.Action}");
};

// 监听单个图层数据变化
layer.DataChanged += (sender, args) =>
{
    if (args.Error != null)
    {
        Console.WriteLine($"Data error: {args.Error.Message}");
    }
    else
    {
        Console.WriteLine("Data loaded successfully");
    }
};

5.10 图层组织最佳实践

5.10.1 图层组织策略

public class LayerOrganizer
{
    public static void OrganizeLayers(Map map)
    {
        // 1. 底图图层(最底层)
        var baseLayer = OpenStreetMap.CreateTileLayer();
        baseLayer.Name = "BaseMap";
        
        // 2. 面图层
        var polygonLayer = new MemoryLayer
        {
            Name = "Polygons",
            Features = LoadPolygons()
        };
        
        // 3. 线图层
        var lineLayer = new MemoryLayer
        {
            Name = "Lines",
            Features = LoadLines()
        };
        
        // 4. 点图层
        var pointLayer = new MemoryLayer
        {
            Name = "Points",
            Features = LoadPoints()
        };
        
        // 5. 标注图层(最顶层)
        var labelLayer = new MemoryLayer
        {
            Name = "Labels",
            Features = LoadLabels()
        };
        
        // 按顺序添加(先添加的在下层)
        map.Layers.Add(baseLayer);
        map.Layers.Add(polygonLayer);
        map.Layers.Add(lineLayer);
        map.Layers.Add(pointLayer);
        map.Layers.Add(labelLayer);
    }
}

5.10.2 图层管理器类

public class LayerManager
{
    private readonly Map _map;
    private readonly Dictionary<string, ILayer> _layerIndex = new();
    
    public LayerManager(Map map)
    {
        _map = map;
    }
    
    public void AddLayer(ILayer layer, int? index = null)
    {
        if (_layerIndex.ContainsKey(layer.Name))
        {
            throw new ArgumentException($"Layer '{layer.Name}' already exists");
        }
        
        if (index.HasValue)
            _map.Layers.Insert(index.Value, layer);
        else
            _map.Layers.Add(layer);
        
        _layerIndex[layer.Name] = layer;
    }
    
    public ILayer? GetLayer(string name)
    {
        return _layerIndex.GetValueOrDefault(name);
    }
    
    public void RemoveLayer(string name)
    {
        if (_layerIndex.TryGetValue(name, out var layer))
        {
            _map.Layers.Remove(layer);
            _layerIndex.Remove(name);
        }
    }
    
    public void SetLayerVisibility(string name, bool visible)
    {
        if (_layerIndex.TryGetValue(name, out var layer))
        {
            layer.Enabled = visible;
        }
    }
    
    public void SetLayerOpacity(string name, double opacity)
    {
        if (_layerIndex.TryGetValue(name, out var layer))
        {
            layer.Opacity = Math.Clamp(opacity, 0, 1);
        }
    }
    
    public void MoveLayerToTop(string name)
    {
        if (_layerIndex.TryGetValue(name, out var layer))
        {
            var currentIndex = _map.Layers.IndexOf(layer);
            var targetIndex = _map.Layers.Count - 1;
            _map.Layers.Move(currentIndex, targetIndex);
        }
    }
}

5.11 本章小结

本章详细介绍了 Mapsui 的图层系统:

  1. 图层类型体系:ILayer 接口、BaseLayer 基类及各种具体实现
  2. MemoryLayer:内存中存储要素的基础图层
  3. WritableLayer:支持动态编辑的可写图层
  4. Layer:基于数据提供者的图层,支持异步数据获取
  5. TileLayer:瓦片图层,支持在线和离线瓦片
  6. ImageLayer:显示单张图片的图层
  7. RasterizingLayer:将矢量图层栅格化以提高性能
  8. MyLocationLayer:显示用户位置的特殊图层
  9. 图层集合管理:添加、删除、排序、可见性控制
  10. 最佳实践:图层组织策略和管理器类设计

在下一章中,我们将学习数据提供者与数据源的使用。

5.12 思考与练习

  1. 解释 MemoryLayer 和 WritableLayer 的区别,各适合什么场景?
  2. 实现一个支持分层可见性控制的图层管理器。
  3. 创建一个离线地图应用,使用 MBTiles 作为底图。
  4. 实现一个热力图图层,使用 RasterizingLayer 优化性能。
  5. 设计一个图层 TOC(Table of Contents)控件,支持显示/隐藏和调整顺序。
posted @ 2026-01-08 14:09  我才是银古  阅读(32)  评论(0)    收藏  举报