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;

image

 

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);
        }
    }
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

image

 

 

 

 

 

 

image

 

 

 

 

 

 

 

image

 

posted @ 2026-09-27 19:29  FredGrit  阅读(4)  评论(0)    收藏  举报