WPF canvas draw USA map include states boundary from PostgreSQL
1.Load shape file in PostGIS
SELECT * FROM public."geoboundaries-usa-adm1_simplified" order by shapename;

2.Read and draw geom data from PostgreSQL
<Window x:Class="WpfApp6.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WpfApp6" mc:Ignorable="d" Title="PostGIS MultiPolygon Draw" WindowState="Maximized"> <Grid> <Canvas x:Name="MainCanvas" Background="#222222" SizeChanged="MainCanvas_SizeChanged"/> </Grid> </Window> using NetTopologySuite.Geometries; using Npgsql; using Npgsql.NetTopologySuite; using System; using System.Collections.Generic; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; namespace WpfApp6 { public partial class MainWindow : Window { private readonly double _minLon = -125; private readonly double _maxLon = -65; private readonly double _minLat = 24; private readonly double _maxLat = 50; private bool _hasLoadedData = false; private bool _isRedrawing = false; private readonly List<(string Name, MultiPolygon Geo)> _stateList = new(); private readonly Dictionary<string, TextBlock> _labelMap = new(); public MainWindow() { InitializeComponent(); } private async void MainCanvas_SizeChanged(object sender, SizeChangedEventArgs e) { if (!_hasLoadedData) { await LoadGeoDataAsync(); } else { _ = RedrawMapSafe(); } } private async Task LoadGeoDataAsync() { string connString = "Host=localhost;Port=5432;Username=postgres;Password=root;Database=GISDB"; var dataSourceBuilder = new NpgsqlDataSourceBuilder(connString); dataSourceBuilder.UseNetTopologySuite(); using var dataSource = dataSourceBuilder.Build(); string sql = @"SELECT shapename, geom FROM public.""geoboundaries-usa-adm1_simplified"""; using var conn = await dataSource.OpenConnectionAsync(); using var cmd = new NpgsqlCommand(sql, conn); using var reader = await cmd.ExecuteReaderAsync(); _stateList.Clear(); while (await reader.ReadAsync()) { string stateName = reader.GetString(0); MultiPolygon multiPoly = (MultiPolygon)reader[1]; _stateList.Add((stateName, multiPoly)); } _hasLoadedData = true; RedrawMap(); } private async Task RedrawMapSafe() { if (_isRedrawing) return; try { _isRedrawing = true; await Dispatcher.InvokeAsync(RedrawMap); } finally { _isRedrawing = false; } } private void RedrawMap() { MainCanvas.Children.Clear(); _labelMap.Clear(); foreach (var item in _stateList) { DrawMultiPolygon(item.Geo, item.Name); DrawStateLabel(item.Geo, item.Name); } } private void DrawMultiPolygon(MultiPolygon multiPolygon, string name) { var pathGeo = new PathGeometry(); pathGeo.FillRule = FillRule.EvenOdd; foreach (NetTopologySuite.Geometries.Polygon polygon in multiPolygon.Geometries) { var outerRing = polygon.ExteriorRing; var figure = new PathFigure(); var points = new PointCollection(); for (int i = 0; i < outerRing.NumPoints; i++) { Coordinate coord = outerRing.GetCoordinateN(i); System.Windows.Point canvasPt = GeoToCanvas(coord.X, coord.Y, name); if (double.IsNaN(canvasPt.X) || double.IsNaN(canvasPt.Y)) continue; points.Add(canvasPt); } if (points.Count == 0) continue; figure.StartPoint = points[0]; var polySeg = new PolyLineSegment(points, true); figure.Segments.Add(polySeg); pathGeo.Figures.Add(figure); foreach (var innerRing in polygon.InteriorRings) { var holeFigure = new PathFigure(); var holePoints = new PointCollection(); for (int i = 0; i < innerRing.NumPoints; i++) { Coordinate coord = innerRing.GetCoordinateN(i); System.Windows.Point canvasPt = GeoToCanvas(coord.X, coord.Y, name); if (double.IsNaN(canvasPt.X) || double.IsNaN(canvasPt.Y)) continue; holePoints.Add(canvasPt); } if (holePoints.Count == 0) continue; holeFigure.StartPoint = holePoints[0]; var holeSeg = new PolyLineSegment(holePoints, true); holeFigure.Segments.Add(holeSeg); pathGeo.Figures.Add(holeFigure); } } System.Windows.Shapes.Path path = new System.Windows.Shapes.Path { Data = pathGeo, Fill = new SolidColorBrush(Color.FromRgb(80, 140, 200)), Stroke = Brushes.White, StrokeThickness = 0.5, Tag = name }; path.MouseEnter += StatePath_MouseEnter; path.MouseLeave += StatePath_MouseLeave; MainCanvas.Children.Add(path); } private void DrawStateLabel(MultiPolygon multiPolygon, string stateName) { Coordinate centerCoord = multiPolygon.Centroid.Coordinate; System.Windows.Point centerPt = GeoToCanvas(centerCoord.X, centerCoord.Y, stateName); if (double.IsNaN(centerPt.X) || double.IsNaN(centerPt.Y)) return; var text = new TextBlock { Text = stateName, Foreground = Brushes.White, FontSize = 9, FontWeight = FontWeights.SemiBold, IsHitTestVisible = false }; text.RenderTransformOrigin = new System.Windows.Point(0.5, 0.5); text.RenderTransform = new ScaleTransform(1, 1); text.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); var size = text.DesiredSize; Canvas.SetLeft(text, centerPt.X - size.Width / 2); Canvas.SetTop(text, centerPt.Y - size.Height / 2); MainCanvas.Children.Add(text); _labelMap[stateName] = text; } private void StatePath_MouseEnter(object sender, MouseEventArgs e) { if (sender is System.Windows.Shapes.Path path && path.Tag is string stateName) { if (_labelMap.TryGetValue(stateName, out var label)) { var st = (ScaleTransform)label.RenderTransform; var dur = TimeSpan.FromMilliseconds(150); st.BeginAnimation(ScaleTransform.ScaleXProperty, new DoubleAnimation(3.0, dur)); st.BeginAnimation(ScaleTransform.ScaleYProperty, new DoubleAnimation(3.0, dur)); label.FontWeight = FontWeights.Bold; } MainCanvas.Cursor = Cursors.Hand; } } private void StatePath_MouseLeave(object sender, MouseEventArgs e) { if (sender is System.Windows.Shapes.Path path && path.Tag is string stateName) { if (_labelMap.TryGetValue(stateName, out var label)) { var st = (ScaleTransform)label.RenderTransform; var dur = TimeSpan.FromMilliseconds(150); st.BeginAnimation(ScaleTransform.ScaleXProperty, new DoubleAnimation(1.0, dur)); st.BeginAnimation(ScaleTransform.ScaleYProperty, new DoubleAnimation(1.0, dur)); label.FontWeight = FontWeights.SemiBold; } MainCanvas.Cursor = Cursors.Arrow; } } private System.Windows.Point GeoToCanvas(double lon, double lat, string stateName) { double canvasW = MainCanvas.ActualWidth; double canvasH = MainCanvas.ActualHeight; if (canvasW < 1 || canvasH < 1) { return new System.Windows.Point(double.NaN, double.NaN); } if (stateName == "Alaska") { double akMinLon = -179.15; double akMaxLon = -130.00; double akMinLat = 51.21; double akMaxLat = 71.37; double scale = 0.20; double xNorm = (lon - akMinLon) / (akMaxLon - akMinLon); double x = xNorm * canvasW * scale + canvasW * 0.02; double yNorm = (lat - akMinLat) / (akMaxLat - akMinLat); double y = canvasH - (yNorm * canvasH * scale + canvasH * 0.02); return new System.Windows.Point(x, y); } if (stateName == "Hawaii") { double hiMinLon = -178.33; double hiMaxLon = -154.81; double hiMinLat = 18.91; double hiMaxLat = 28.40; double scale = 0.35; double xNorm = (lon - hiMinLon) / (hiMaxLon - hiMinLon); double x = xNorm * canvasW * scale + canvasW * (1 - scale - 0.02); double yNorm = (lat - hiMinLat) / (hiMaxLat - hiMinLat); double y = canvasH - (yNorm * canvasH * scale + canvasH * 0.02); return new System.Windows.Point(x, y); } double xUs = (lon - _minLon) / (_maxLon - _minLon) * canvasW; double yUs = canvasH - (lat - _minLat) / (_maxLat - _minLat) * canvasH; return new System.Windows.Point(xUs, yUs); } } }




浙公网安备 33010602011771号