第16章 - 实战案例与项目应用

第16章:实战案例与项目应用

16.1 案例一:POI 查询应用

16.1.1 需求分析

创建一个 POI(兴趣点)查询应用,支持:

  • 显示底图
  • 搜索 POI
  • 点击查看详情
  • 导航定位

16.1.2 实现代码

public class PoiMapService
{
    private readonly Map _map;
    private readonly WritableLayer _poiLayer;
    private readonly WritableLayer _searchResultLayer;
    
    public PoiMapService()
    {
        _map = new Map();
        _map.Layers.Add(OpenStreetMap.CreateTileLayer());
        
        _poiLayer = new WritableLayer { Name = "POI" };
        _searchResultLayer = new WritableLayer { Name = "SearchResults" };
        
        _map.Layers.Add(_poiLayer);
        _map.Layers.Add(_searchResultLayer);
        
        SetupWidgets();
        SetupInfoEvent();
    }
    
    public Map Map => _map;
    
    public event Action<PoiInfo> PoiClicked;
    
    private void SetupWidgets()
    {
        _map.Widgets.Add(new ZoomInOutWidget
        {
            HorizontalAlignment = HorizontalAlignment.Right,
            VerticalAlignment = VerticalAlignment.Top,
            MarginX = 20,
            MarginY = 20
        });
        
        _map.Widgets.Add(new ScaleBarWidget(_map)
        {
            HorizontalAlignment = HorizontalAlignment.Left,
            VerticalAlignment = VerticalAlignment.Bottom,
            MarginX = 20,
            MarginY = 20
        });
    }
    
    private void SetupInfoEvent()
    {
        _map.Info += (sender, args) =>
        {
            if (args.MapInfo?.Feature != null)
            {
                var feature = args.MapInfo.Feature;
                var poi = new PoiInfo
                {
                    Name = feature["name"]?.ToString(),
                    Category = feature["category"]?.ToString(),
                    Address = feature["address"]?.ToString()
                };
                PoiClicked?.Invoke(poi);
            }
        };
    }
    
    public void LoadPois(IEnumerable<PoiData> pois)
    {
        _poiLayer.Clear();
        
        foreach (var poi in pois)
        {
            var point = SphericalMercator.FromLonLat(poi.Lon, poi.Lat).ToMPoint();
            var feature = new PointFeature(point);
            feature["name"] = poi.Name;
            feature["category"] = poi.Category;
            feature["address"] = poi.Address;
            feature.Styles = new[] { GetPoiStyle(poi.Category) };
            
            _poiLayer.Add(feature);
        }
        
        _poiLayer.DataHasChanged();
    }
    
    public void Search(string keyword, IEnumerable<PoiData> results)
    {
        _searchResultLayer.Clear();
        
        foreach (var poi in results)
        {
            var point = SphericalMercator.FromLonLat(poi.Lon, poi.Lat).ToMPoint();
            var feature = new PointFeature(point);
            feature["name"] = poi.Name;
            feature.Styles = new[] { CreateSearchResultStyle() };
            
            _searchResultLayer.Add(feature);
        }
        
        _searchResultLayer.DataHasChanged();
        
        // 缩放到搜索结果范围
        if (_searchResultLayer.Extent != null)
        {
            _map.Navigator.ZoomToBox(_searchResultLayer.Extent);
        }
    }
    
    public void NavigateTo(double lon, double lat, int zoomLevel = 16)
    {
        var center = SphericalMercator.FromLonLat(lon, lat);
        var resolution = ZoomLevelResolutions.GetResolution(zoomLevel);
        _map.Navigator.CenterOnAndZoomTo(
            new MPoint(center.X, center.Y),
            resolution,
            duration: 500
        );
    }
    
    private IStyle GetPoiStyle(string category)
    {
        var color = category switch
        {
            "餐饮" => Color.Orange,
            "酒店" => Color.Blue,
            "购物" => Color.Green,
            "景点" => Color.Red,
            _ => Color.Gray
        };
        
        return new SymbolStyle
        {
            SymbolScale = 0.5,
            Fill = new Brush(color),
            Outline = new Pen(Color.White, 2)
        };
    }
    
    private IStyle CreateSearchResultStyle()
    {
        return new SymbolStyle
        {
            SymbolScale = 0.6,
            Fill = new Brush(Color.Yellow),
            Outline = new Pen(Color.Red, 2)
        };
    }
}

public class PoiData
{
    public string Name { get; set; }
    public string Category { get; set; }
    public string Address { get; set; }
    public double Lon { get; set; }
    public double Lat { get; set; }
}

public class PoiInfo
{
    public string Name { get; set; }
    public string Category { get; set; }
    public string Address { get; set; }
}

16.2 案例二:轨迹追踪应用

16.2.1 需求分析

创建一个轨迹追踪应用,支持:

  • 实时位置显示
  • 轨迹记录和显示
  • 统计信息(距离、时间)

16.2.2 实现代码

public class TrackingService
{
    private readonly Map _map;
    private readonly WritableLayer _trackLayer;
    private readonly WritableLayer _currentPositionLayer;
    private readonly List<MPoint> _trackPoints = new();
    private DateTime _startTime;
    
    public TrackingService()
    {
        _map = new Map();
        _map.Layers.Add(OpenStreetMap.CreateTileLayer());
        
        _trackLayer = new WritableLayer { Name = "Track" };
        _currentPositionLayer = new WritableLayer { Name = "CurrentPosition" };
        
        _map.Layers.Add(_trackLayer);
        _map.Layers.Add(_currentPositionLayer);
    }
    
    public Map Map => _map;
    public bool IsTracking { get; private set; }
    public double TotalDistance { get; private set; }
    public TimeSpan Duration => DateTime.Now - _startTime;
    
    public void StartTracking()
    {
        IsTracking = true;
        _trackPoints.Clear();
        TotalDistance = 0;
        _startTime = DateTime.Now;
        
        _trackLayer.Clear();
        _trackLayer.DataHasChanged();
    }
    
    public void StopTracking()
    {
        IsTracking = false;
    }
    
    public void UpdatePosition(double lon, double lat)
    {
        var point = SphericalMercator.FromLonLat(lon, lat).ToMPoint();
        
        // 更新当前位置
        UpdateCurrentPosition(point);
        
        if (IsTracking)
        {
            // 计算距离
            if (_trackPoints.Count > 0)
            {
                var lastPoint = _trackPoints.Last();
                TotalDistance += CalculateDistance(lastPoint, point);
            }
            
            // 添加轨迹点
            _trackPoints.Add(point);
            UpdateTrackLine();
        }
        
        // 地图跟随
        _map.Navigator.CenterOn(point);
    }
    
    private void UpdateCurrentPosition(MPoint point)
    {
        _currentPositionLayer.Clear();
        
        var feature = new PointFeature(point);
        feature.Styles = new[]
        {
            // 外圈
            new SymbolStyle
            {
                SymbolScale = 1.0,
                Fill = new Brush(new Color(0, 120, 255, 50))
            },
            // 中心点
            new SymbolStyle
            {
                SymbolScale = 0.3,
                Fill = new Brush(new Color(0, 120, 255)),
                Outline = new Pen(Color.White, 2)
            }
        };
        
        _currentPositionLayer.Add(feature);
        _currentPositionLayer.DataHasChanged();
    }
    
    private void UpdateTrackLine()
    {
        if (_trackPoints.Count < 2) return;
        
        _trackLayer.Clear();
        
        var factory = new GeometryFactory();
        var coordinates = _trackPoints
            .Select(p => new Coordinate(p.X, p.Y))
            .ToArray();
        
        var line = factory.CreateLineString(coordinates);
        var feature = new GeometryFeature(line);
        feature.Styles = new[]
        {
            new VectorStyle
            {
                Line = new Pen(new Color(0, 120, 255), 4)
                {
                    StrokeCap = PenStrokeCap.Round,
                    StrokeJoin = StrokeJoin.Round
                }
            }
        };
        
        _trackLayer.Add(feature);
        _trackLayer.DataHasChanged();
    }
    
    private double CalculateDistance(MPoint p1, MPoint p2)
    {
        var lonLat1 = SphericalMercator.ToLonLat(p1.X, p1.Y);
        var lonLat2 = SphericalMercator.ToLonLat(p2.X, p2.Y);
        
        return DistanceCalculator.HaversineDistance(
            lonLat1.Y, lonLat1.X,
            lonLat2.Y, lonLat2.X
        );
    }
    
    public TrackStatistics GetStatistics()
    {
        return new TrackStatistics
        {
            TotalDistance = TotalDistance,
            Duration = Duration,
            AverageSpeed = Duration.TotalHours > 0 
                ? (TotalDistance / 1000) / Duration.TotalHours 
                : 0,
            PointCount = _trackPoints.Count
        };
    }
}

public class TrackStatistics
{
    public double TotalDistance { get; set; }  // 米
    public TimeSpan Duration { get; set; }
    public double AverageSpeed { get; set; }   // km/h
    public int PointCount { get; set; }
}

16.3 案例三:区域绘制与分析

16.3.1 需求分析

创建一个区域分析工具,支持:

  • 绘制多边形区域
  • 计算面积
  • 统计区域内的要素

16.3.2 实现代码

public class RegionAnalysisService
{
    private readonly Map _map;
    private readonly WritableLayer _drawingLayer;
    private readonly WritableLayer _analysisLayer;
    private readonly List<MPoint> _currentPolygonPoints = new();
    private bool _isDrawing;
    
    public RegionAnalysisService()
    {
        _map = new Map();
        _map.Layers.Add(OpenStreetMap.CreateTileLayer());
        
        _drawingLayer = new WritableLayer { Name = "Drawing" };
        _analysisLayer = new WritableLayer { Name = "Analysis" };
        
        _map.Layers.Add(_analysisLayer);
        _map.Layers.Add(_drawingLayer);
        
        _map.Info += OnMapInfo;
    }
    
    public Map Map => _map;
    public event Action<RegionAnalysisResult> AnalysisCompleted;
    
    public void StartDrawing()
    {
        _isDrawing = true;
        _currentPolygonPoints.Clear();
        _drawingLayer.Clear();
        _drawingLayer.DataHasChanged();
    }
    
    public void FinishDrawing()
    {
        if (_currentPolygonPoints.Count < 3)
        {
            _isDrawing = false;
            return;
        }
        
        _isDrawing = false;
        
        // 创建多边形
        var polygon = CreatePolygon(_currentPolygonPoints);
        
        // 执行分析
        var result = AnalyzeRegion(polygon);
        
        // 显示分析结果
        ShowAnalysisResult(polygon, result);
        
        AnalysisCompleted?.Invoke(result);
        
        _currentPolygonPoints.Clear();
    }
    
    public void CancelDrawing()
    {
        _isDrawing = false;
        _currentPolygonPoints.Clear();
        _drawingLayer.Clear();
        _drawingLayer.DataHasChanged();
    }
    
    private void OnMapInfo(object sender, MapInfoEventArgs e)
    {
        if (!_isDrawing || e.MapInfo?.WorldPosition == null) return;
        
        _currentPolygonPoints.Add(e.MapInfo.WorldPosition);
        UpdateDrawingPreview();
    }
    
    private void UpdateDrawingPreview()
    {
        _drawingLayer.Clear();
        
        // 绘制顶点
        foreach (var point in _currentPolygonPoints)
        {
            var vertexFeature = new PointFeature(point);
            vertexFeature.Styles = new[]
            {
                new SymbolStyle
                {
                    SymbolScale = 0.3,
                    Fill = new Brush(Color.White),
                    Outline = new Pen(Color.Blue, 2)
                }
            };
            _drawingLayer.Add(vertexFeature);
        }
        
        // 绘制多边形预览
        if (_currentPolygonPoints.Count >= 3)
        {
            var polygon = CreatePolygon(_currentPolygonPoints);
            var polygonFeature = new GeometryFeature(polygon);
            polygonFeature.Styles = new[]
            {
                new VectorStyle
                {
                    Fill = new Brush(new Color(0, 120, 255, 50)),
                    Outline = new Pen(new Color(0, 120, 255), 2)
                }
            };
            _drawingLayer.Add(polygonFeature);
        }
        else if (_currentPolygonPoints.Count == 2)
        {
            // 绘制线
            var factory = new GeometryFactory();
            var line = factory.CreateLineString(
                _currentPolygonPoints.Select(p => new Coordinate(p.X, p.Y)).ToArray()
            );
            var lineFeature = new GeometryFeature(line);
            lineFeature.Styles = new[]
            {
                new VectorStyle
                {
                    Line = new Pen(new Color(0, 120, 255), 2)
                }
            };
            _drawingLayer.Add(lineFeature);
        }
        
        _drawingLayer.DataHasChanged();
    }
    
    private Polygon CreatePolygon(IEnumerable<MPoint> points)
    {
        var factory = new GeometryFactory();
        var coords = points
            .Select(p => new Coordinate(p.X, p.Y))
            .Concat(new[] { new Coordinate(points.First().X, points.First().Y) })
            .ToArray();
        
        return factory.CreatePolygon(coords);
    }
    
    private RegionAnalysisResult AnalyzeRegion(Polygon polygon)
    {
        // 计算面积(球面面积)
        var area = CalculateSphericalArea(polygon);
        
        // 计算周长
        var perimeter = CalculatePerimeter(polygon);
        
        // 统计区域内的要素
        var featureCount = CountFeaturesInRegion(polygon);
        
        return new RegionAnalysisResult
        {
            Area = area,
            Perimeter = perimeter,
            FeatureCount = featureCount
        };
    }
    
    private double CalculateSphericalArea(Polygon polygon)
    {
        // 简化计算:转换为经纬度后计算
        var coords = polygon.Coordinates
            .Select(c => SphericalMercator.ToLonLat(c.X, c.Y))
            .ToList();
        
        // 使用球面多边形面积公式
        return AreaCalculator.CalculateSphericalAreaFromLonLat(coords);
    }
    
    private double CalculatePerimeter(Polygon polygon)
    {
        double perimeter = 0;
        var coords = polygon.Coordinates;
        
        for (int i = 0; i < coords.Length - 1; i++)
        {
            var p1 = SphericalMercator.ToLonLat(coords[i].X, coords[i].Y);
            var p2 = SphericalMercator.ToLonLat(coords[i + 1].X, coords[i + 1].Y);
            
            perimeter += DistanceCalculator.HaversineDistance(
                p1.Y, p1.X, p2.Y, p2.X
            );
        }
        
        return perimeter;
    }
    
    private int CountFeaturesInRegion(Polygon polygon)
    {
        var count = 0;
        
        foreach (var layer in _map.Layers.OfType<MemoryLayer>())
        {
            if (layer.Name == "Drawing" || layer.Name == "Analysis") continue;
            
            foreach (var feature in layer.Features ?? Enumerable.Empty<IFeature>())
            {
                if (feature is GeometryFeature gf && 
                    gf.Geometry != null &&
                    polygon.Contains(gf.Geometry))
                {
                    count++;
                }
            }
        }
        
        return count;
    }
    
    private void ShowAnalysisResult(Polygon polygon, RegionAnalysisResult result)
    {
        _analysisLayer.Clear();
        
        var feature = new GeometryFeature(polygon);
        feature.Styles = new[]
        {
            new VectorStyle
            {
                Fill = new Brush(new Color(0, 255, 0, 80)),
                Outline = new Pen(Color.Green, 3)
            }
        };
        
        _analysisLayer.Add(feature);
        _analysisLayer.DataHasChanged();
    }
}

public class RegionAnalysisResult
{
    public double Area { get; set; }           // 平方米
    public double Perimeter { get; set; }      // 米
    public int FeatureCount { get; set; }
    
    public string AreaText => Area switch
    {
        >= 1000000 => $"{Area / 1000000:F2} km²",
        >= 10000 => $"{Area / 10000:F2} 公顷",
        _ => $"{Area:F0} m²"
    };
    
    public string PerimeterText => Perimeter >= 1000
        ? $"{Perimeter / 1000:F2} km"
        : $"{Perimeter:F0} m";
}

16.4 本章小结

本章通过三个实战案例展示了 Mapsui 的综合应用:

  1. POI 查询应用:地图显示、搜索定位、点击交互
  2. 轨迹追踪应用:实时位置、轨迹记录、统计分析
  3. 区域分析工具:多边形绘制、面积计算、要素统计

这些案例涵盖了:

  • 图层管理与样式配置
  • 用户交互与事件处理
  • 空间分析与计算
  • 跨平台代码设计

16.5 总结与展望

通过本教程的学习,您已经掌握了 Mapsui 的核心概念和使用方法:

  1. 地图基础:Map、MapControl、Viewport
  2. 图层系统:各种图层类型和管理方法
  3. 样式系统:符号、线、面、标签样式
  4. 数据源:内存数据、文件数据、网络服务
  5. 交互处理:事件、绘制、编辑
  6. 性能优化:缓存、简化、异步加载
  7. 跨平台集成:WPF、MAUI、Avalonia、Blazor

建议继续学习:

  • 深入研究 NTS 几何处理
  • 探索自定义渲染器开发
  • 学习 GIS 空间分析算法
  • 关注 Mapsui 社区最新动态

16.6 参考资源

资源 URL
Mapsui 官网 https://mapsui.com
GitHub https://github.com/Mapsui/Mapsui
在线示例 https://mapsui.com/v5/samples
API 文档 https://mapsui.com/v5/api
NTS 文档 https://nettopologysuite.github.io

感谢您学习本教程!祝您在 GIS 开发道路上取得成功!

posted @ 2026-01-08 14:40  我才是银古  阅读(13)  评论(0)    收藏  举报