第14章 - 性能优化与最佳实践

第14章:性能优化与最佳实践

14.1 性能优化原则

14.1.1 性能瓶颈分析

┌─────────────────────────────────────────────────────────────────┐
│                      性能瓶颈来源                                │
├─────────────────────────────────────────────────────────────────┤
│  数据获取         │  渲染性能         │  内存管理               │
│  ─────────       │  ─────────       │  ─────────             │
│  • 网络请求       │  • 要素数量过多   │  • 瓦片缓存不当         │
│  • 数据解析       │  • 复杂几何       │  • 要素重复创建         │
│  • 数据库查询     │  • 样式计算       │  • 事件订阅泄漏         │
└─────────────────────────────────────────────────────────────────┘

14.1.2 性能监控

// 启用性能小部件
var performanceWidget = new PerformanceWidget
{
    HorizontalAlignment = HorizontalAlignment.Left,
    VerticalAlignment = VerticalAlignment.Top,
    MarginX = 10,
    MarginY = 10
};
map.Widgets.Add(performanceWidget);

// 自定义性能监控
public class PerformanceMonitor
{
    private readonly Stopwatch _stopwatch = new();
    private readonly List<double> _frameTimes = new();
    
    public void StartFrame()
    {
        _stopwatch.Restart();
    }
    
    public void EndFrame()
    {
        _stopwatch.Stop();
        _frameTimes.Add(_stopwatch.Elapsed.TotalMilliseconds);
        
        if (_frameTimes.Count > 100)
            _frameTimes.RemoveAt(0);
    }
    
    public double AverageFrameTime => _frameTimes.Average();
    public double FPS => 1000.0 / AverageFrameTime;
}

14.2 数据层优化

14.2.1 减少要素数量

// 视口裁剪:只加载视口范围内的要素
public IEnumerable<IFeature> GetVisibleFeatures(
    IEnumerable<IFeature> allFeatures,
    MRect viewport)
{
    return allFeatures.Where(f => 
        f.Extent?.Intersects(viewport) == true
    );
}

// 按缩放级别过滤
public IEnumerable<IFeature> FilterByZoomLevel(
    IEnumerable<IFeature> features,
    double resolution)
{
    var zoomLevel = ZoomLevelResolutions.GetZoomLevel(resolution);
    
    return features.Where(f =>
    {
        var minZoom = f["minZoom"] as int? ?? 0;
        var maxZoom = f["maxZoom"] as int? ?? 20;
        return zoomLevel >= minZoom && zoomLevel <= maxZoom;
    });
}

14.2.2 几何简化

using NetTopologySuite.Simplify;

// 根据分辨率简化几何
public Geometry SimplifyForDisplay(Geometry geometry, double resolution)
{
    // 计算合适的简化容差
    var tolerance = resolution * 2;  // 2 像素容差
    
    return TopologyPreservingSimplifier.Simplify(geometry, tolerance);
}

// 缓存简化后的几何
public class SimplifiedGeometryCache
{
    private readonly Dictionary<(Geometry, int), Geometry> _cache = new();
    
    public Geometry GetSimplified(Geometry geometry, double resolution)
    {
        var zoomLevel = ZoomLevelResolutions.GetZoomLevel(resolution);
        var key = (geometry, zoomLevel);
        
        if (!_cache.TryGetValue(key, out var simplified))
        {
            var tolerance = GetToleranceForZoom(zoomLevel);
            simplified = TopologyPreservingSimplifier.Simplify(geometry, tolerance);
            _cache[key] = simplified;
        }
        
        return simplified;
    }
    
    private double GetToleranceForZoom(int zoom) => zoom switch
    {
        < 5 => 10000,
        < 10 => 1000,
        < 15 => 100,
        _ => 10
    };
}

14.2.3 数据分页和懒加载

// 分页加载大数据集
public class PaginatedDataLoader
{
    private readonly int _pageSize;
    private readonly Func<int, int, IEnumerable<IFeature>> _loader;
    
    public PaginatedDataLoader(
        Func<int, int, IEnumerable<IFeature>> loader,
        int pageSize = 1000)
    {
        _loader = loader;
        _pageSize = pageSize;
    }
    
    public async IAsyncEnumerable<IFeature> LoadAllAsync()
    {
        int page = 0;
        while (true)
        {
            var features = await Task.Run(() => 
                _loader(page * _pageSize, _pageSize).ToList()
            );
            
            if (features.Count == 0) break;
            
            foreach (var feature in features)
                yield return feature;
            
            page++;
        }
    }
}

14.3 渲染优化

14.3.1 使用 RasterizingLayer

// 将复杂矢量图层栅格化
var complexLayer = new MemoryLayer
{
    Features = GetComplexFeatures()  // 大量复杂几何
};

var rasterizedLayer = new RasterizingLayer(complexLayer)
{
    Name = "RasterizedComplexLayer"
};

map.Layers.Add(rasterizedLayer);

14.3.2 样式优化

// 避免在每次渲染时创建新样式
// 不好的做法:
feature.Styles = new[] { new SymbolStyle { Fill = new Brush(Color.Red) } };

// 好的做法:预先创建样式并重用
private static readonly IStyle _pointStyle = new SymbolStyle
{
    SymbolScale = 0.5,
    Fill = new Brush(Color.Red)
};

feature.Styles = new[] { _pointStyle };

14.3.3 减少重绘

// 批量更新要素
public void UpdateFeatures(WritableLayer layer, IEnumerable<IFeature> features)
{
    // 不好的做法:每次添加都触发重绘
    foreach (var feature in features)
    {
        layer.Add(feature);
        layer.DataHasChanged();  // 每次都重绘
    }
    
    // 好的做法:批量操作后统一重绘
    foreach (var feature in features)
    {
        layer.Add(feature);
    }
    layer.DataHasChanged();  // 只重绘一次
}

14.4 瓦片缓存优化

14.4.1 配置合理的缓存

// 内存缓存
var memoryCache = new MemoryCache<byte[]>(
    minTiles: 100,
    maxTiles: 500
);

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

var fileCache = new FileCache(cacheDir, "png")
{
    MaxTileCount = 10000  // 最大缓存瓦片数
};

// 组合缓存
var tileSource = new HttpTileSource(
    new GlobalSphericalMercator(),
    "https://tile.openstreetmap.org/{z}/{x}/{y}.png",
    persistentCache: fileCache
);

14.4.2 预加载瓦片

public class TilePreloader
{
    private readonly ITileSource _tileSource;
    
    public async Task PreloadAreaAsync(MRect extent, int minZoom, int maxZoom)
    {
        for (int zoom = minZoom; zoom <= maxZoom; zoom++)
        {
            var tiles = GetTilesInExtent(extent, zoom);
            
            await Parallel.ForEachAsync(tiles, async (tile, ct) =>
            {
                try
                {
                    await _tileSource.GetTileAsync(tile);
                }
                catch { /* 忽略错误 */ }
            });
        }
    }
    
    private IEnumerable<TileInfo> GetTilesInExtent(MRect extent, int zoom)
    {
        // 计算范围内的所有瓦片
        var schema = new GlobalSphericalMercator();
        return schema.GetTileInfos(extent, zoom);
    }
}

14.5 内存管理

14.5.1 及时释放资源

public class MapManager : IDisposable
{
    private Map _map;
    private bool _disposed;
    
    public void Dispose()
    {
        if (_disposed) return;
        
        // 取消事件订阅
        _map.Info -= OnMapInfo;
        _map.DataChanged -= OnDataChanged;
        
        // 清除图层
        foreach (var layer in _map.Layers.ToList())
        {
            _map.Layers.Remove(layer);
            (layer as IDisposable)?.Dispose();
        }
        
        // 清除小部件
        _map.Widgets.Clear();
        
        _map.Dispose();
        _disposed = true;
    }
}

14.5.2 避免内存泄漏

// 使用弱引用避免循环引用
public class WeakEventHandler
{
    private readonly WeakReference<Map> _mapRef;
    
    public WeakEventHandler(Map map)
    {
        _mapRef = new WeakReference<Map>(map);
    }
    
    public void OnMapInfo(object sender, MapInfoEventArgs e)
    {
        if (_mapRef.TryGetTarget(out var map))
        {
            // 处理事件
        }
    }
}

14.6 最佳实践

14.6.1 图层组织

// 合理组织图层顺序
public void SetupLayers(Map map)
{
    // 1. 底图(最下层)
    map.Layers.Add(CreateBaseLayer());
    
    // 2. 面图层
    map.Layers.Add(CreatePolygonLayer());
    
    // 3. 线图层
    map.Layers.Add(CreateLineLayer());
    
    // 4. 点图层
    map.Layers.Add(CreatePointLayer());
    
    // 5. 标注图层(最上层)
    map.Layers.Add(CreateLabelLayer());
}

// 使用适当的缩放限制
layer.MinVisible = ZoomLevelResolutions.GetResolution(18);
layer.MaxVisible = ZoomLevelResolutions.GetResolution(5);

14.6.2 异步操作

// 数据加载应该是异步的
public async Task LoadDataAsync()
{
    // 显示加载指示
    ShowLoading();
    
    try
    {
        // 在后台线程加载数据
        var features = await Task.Run(() => LoadFeaturesFromDatabase());
        
        // 在 UI 线程更新图层
        await Dispatcher.InvokeAsync(() =>
        {
            _dataLayer.Features = features;
            _dataLayer.DataHasChanged();
        });
    }
    finally
    {
        HideLoading();
    }
}

14.6.3 错误处理

// 全局错误处理
Mapsui.Logging.Logger.LogDelegate += (level, message, ex) =>
{
    if (level == Mapsui.Logging.LogLevel.Error)
    {
        // 记录错误日志
        _logger.LogError(ex, $"Mapsui error: {message}");
    }
};

// 数据加载错误处理
map.DataChanged += (sender, args) =>
{
    if (args.Error != null)
    {
        HandleDataError(args.Error);
    }
};

14.7 常见问题诊断

问题 可能原因 解决方案
地图卡顿 要素过多 简化几何、分级显示、栅格化
内存占用高 缓存过大 调整缓存大小、及时释放
加载慢 网络请求多 使用缓存、预加载
渲染模糊 分辨率不匹配 检查 DPI 设置

14.8 本章小结

本章详细介绍了 Mapsui 的性能优化:

  1. 性能瓶颈分析:数据获取、渲染、内存管理
  2. 数据层优化:视口裁剪、几何简化、分页加载
  3. 渲染优化:栅格化、样式重用、批量更新
  4. 瓦片缓存:内存缓存、文件缓存、预加载
  5. 内存管理:资源释放、避免泄漏
  6. 最佳实践:图层组织、异步操作、错误处理

在下一章中,我们将学习各平台集成实战。

14.9 思考与练习

  1. 实现一个自适应细节层次(LOD)系统。
  2. 创建一个性能分析工具,统计各图层的渲染时间。
  3. 实现一个智能缓存管理器,自动清理过期缓存。
  4. 优化一个包含 100000+ 要素的地图应用。
  5. 实现后台瓦片预加载功能。
posted @ 2026-01-08 14:40  我才是银古  阅读(18)  评论(0)    收藏  举报