第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 的综合应用:
- POI 查询应用:地图显示、搜索定位、点击交互
- 轨迹追踪应用:实时位置、轨迹记录、统计分析
- 区域分析工具:多边形绘制、面积计算、要素统计
这些案例涵盖了:
- 图层管理与样式配置
- 用户交互与事件处理
- 空间分析与计算
- 跨平台代码设计
16.5 总结与展望
通过本教程的学习,您已经掌握了 Mapsui 的核心概念和使用方法:
- 地图基础:Map、MapControl、Viewport
- 图层系统:各种图层类型和管理方法
- 样式系统:符号、线、面、标签样式
- 数据源:内存数据、文件数据、网络服务
- 交互处理:事件、绘制、编辑
- 性能优化:缓存、简化、异步加载
- 跨平台集成: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 开发道路上取得成功!

浙公网安备 33010602011771号