使用 GMap.NET 实现添加标注、移动标注功能。(WPF版)

前言

在WPF嵌入地图,有两种方式: 浏览器方式;控件方式。

1)浏览器方式就是使用浏览器控件WebBrowser,设置好网址就行了。这种方式与地图的交互不太直接,需要懂html、javascript。对于不懂web编程的开发者来说,有点困难。

2)控件方式就是使用第三方控件;不需要处了解web相关知识,使用起来比较直接,易于理解。GMap.net 类库就实现了这种控件。

GMap.net 简介

GMap.NET 是一个强大、免费、跨平台、开源的.NET控件,它在Windows Forms 和WPF环境中能够通过Google, Yahoo!, Bing, OpenStreetMap, ArcGIS, Pergo, SigPac等实现寻找路径、地理编码以及地图展示功能,并支持缓存和运行在Mobile环境中。

GMap.NET多年前已经存在,最初主要支持WinForm。WPF出现的较晚;但是,现在这个控件也可用于WPF开发。不过,网上相关WPF开发的例子较少。因为工作需要,最近使用这个控件开发了gis相关项目,把开发过程中的使用技巧写出来,以供参考!

其中部分代码参考了别人的文章,稍作修改!

程序界面:

将GMap.net加入项目

使用NuGet,搜索GMap.net就可以找到该控件:

 

添加地图

GMap.net是国外开发的,不过也能很好的支持国内地图。这个控件是开放的,只要按照要求完成相关设置,就可以把各类地图加进来。

要理解这些设置,就需要先理解地图的基本知识。我在这里就不多述。简单一句话句话就是:地图其实就多个图片拼接而来的;你需要告诉控件,如何根据地理坐标和缩放级别获取对应的图片就行。

以高德地图为例,看看如何设置

需要重写GMapProvider这个类,代码如下:

    public abstract class AMapProviderBase : GMapProvider
    {
        public AMapProviderBase()
        {
            MaxZoom = null;
            RefererUrl = "http://www.amap.com/";
            Copyright = string.Format("©{0} 高德 Corporation, ©{0} NAVTEQ, ©{0} Image courtesy of NASA", DateTime.Today.Year);
        }

        public override PureProjection Projection
        {
            get { return MercatorProjection.Instance; }
        }

        GMapProvider[] overlays;
        public override GMapProvider[] Overlays
        {
            get
            {
                if (overlays == null)
                {
                    overlays = new GMapProvider[] { this };//只有本图层
                }
                return overlays;
            }
        }
    }

    public class AMapProvider : AMapProviderBase
    {
        public static readonly AMapProvider Instance;

        readonly Guid id = new Guid("EF3DD303-3F74-4938-BF40-232D0595EE88");
        public override Guid Id
        {
            get { return id; }
        }

        readonly string name = "AMap";
        public override string Name
        {
            get
            {
                return name;
            }
        }

        private AMapProvider()
        {

        }
        static AMapProvider()
        {
            Instance = new AMapProvider();
        }

        //根据坐标和缩放,获取对应的图片。
        public override PureImage GetTileImage(GPoint pos, int zoom)
        {
            string url = MakeTileImageUrl(pos, zoom, LanguageStr);
            return GetTileImageUsingHttp(url);
        }

        string MakeTileImageUrl(GPoint pos, int zoom, string language)
        {

            //http://webrd04.is.autonavi.com/appmaptile?x=5&y=2&z=3&lang=zh_cn&size=1&scale=1&style=7
            string url = string.Format(UrlFormat, pos.X, pos.Y, zoom);
            Console.WriteLine("url:" + url);
            return url;
        }

        static readonly string UrlFormat = "http://webrd04.is.autonavi.com/appmaptile?x={0}&y={1}&z={2}&lang=zh_cn&size=1&scale=1&style=7";
    }

最重要的函数就是 public override PureImage GetTileImage(GPoint pos, int zoom),地图就是同一缩放比例的图片堆砌而来。

使用控件

 在窗口中添加控件:主窗口代码如下

<Window x:Class="GMapTest.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:gmap="clr-namespace:GMap.NET.WindowsPresentation;assembly=GMap.NET.WindowsPresentation"
        xmlns:local="clr-namespace:GMapTest" Loaded="Window_Loaded"
        mc:Ignorable="d"  Background="#5A9EA5"  
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="31*"/>
            <ColumnDefinition Width="167*"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"></RowDefinition>
            <RowDefinition></RowDefinition>
        </Grid.RowDefinitions>

        <StackPanel Margin="5" Orientation="Horizontal" Grid.ColumnSpan="2">
            <CheckBox x:Name="checkMoveFlag" Margin="5,2,2,2" Click="CheckMoveFlag_Click">标注可移动</CheckBox>
            <CheckBox x:Name="checkAddFlag" Margin="10,2,2,2">添加标注</CheckBox>
        </StackPanel>

        <GroupBox Grid.Row="1" Margin="0" Grid.ColumnSpan="2">
            <gmap:GMapControl x:Name="MainMap" MaxZoom="24" MinZoom="1" 
                          RenderOptions.BitmapScalingMode="NearestNeighbor" 
                              UseLayoutRounding="True" SnapsToDevicePixels="True">
            </gmap:GMapControl>
        </GroupBox>
    </Grid>
</Window>

使用设置RenderOptions.BitmapScalingMode="NearestNeighbor",可使图片显示较为清晰。

添加标注

标注称之为Marker。控件有一个属性 public ObservableCollection<GMapMarker> Markers { get; }用于存放标注。添加标注就是设置好GMapMarker相关属性就行。代码如下:

      BitmapImage _pinSrcImage;
        Image CreatePinImage(GMapMarker marker)
        {
            Image img = new Image();
            img.Tag = marker;
            img.Width = 32;
            img.Height = 32;

            if (_pinSrcImage == null)
            {
                //多个标注共用一个图像源,节省内存。
                _pinSrcImage = new BitmapImage(new Uri("pack://application:,,,/AMap/red-dot.png", UriKind.Absolute));
                _pinSrcImage.Freeze();
            }
            img.Source = _pinSrcImage;
            //鼠标热点位置
            marker.Offset = new Point(-img.Width / 2, -img.Height / 2);
            return img;
        }

        private void AddMaker(PointLatLng pt)
        {
            GMapMarker marker = new GMapMarker(pt);
            marker.Shape = CreatePinImage(marker);

            //将图层添加到地图
            this.MainMap.Markers.Add(marker);
        }

 移动标注

首先需要检测鼠标是否点击了标注部分。需要在MouseDown事件中,通过WPF视觉树辅助函数来判断(VisualTreeHelper.HitTest)。其次在MouseMove函数中,将标注移动到新的坐标点。这里是通过鼠标左键移动;要实现此操作,设置控件拖动方式为 MainMap.DragButton = MouseButton.Right; 暨设置地图拖动方式为鼠标右键,防止与标注移动相冲突。

关联控件事件:

    MainMap.MouseMove += MainMap_MouseMove;
    MainMap.MouseDown += MainMap_MouseDown;
MainMap.MouseLeftButtonUp += MainMap_MouseLeftButtonUp;

判断鼠标是否点击了标注部分

        GMapMarker _currentElement;
        private void MainMap_MouseDown(object sender, MouseButtonEventArgs e)
        {
            if (checkMoveFlag.IsChecked == false)
            {
                return;
            }

            //判断是否点击了标注
            if (_currentElement == null)
            {
                Point pt = e.GetPosition(MainMap);
                PointLatLng point = MainMap.FromLocalToLatLng((int)pt.X, (int)pt.Y);

                PointHitTestParameters parameters = new PointHitTestParameters(pt);
                VisualTreeHelper.HitTest(MainMap, null, HitTestCallback, parameters);
            }
        }
        //右键弹起,设置标注变量为空
        private void MainMap_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            _currentElement = null;
        }

        private HitTestResultBehavior HitTestCallback(HitTestResult result)
        {
            Image image = result.VisualHit as Image;
            if (image != null)
            {
                _currentElement = image.Tag as GMapMarker;
                return HitTestResultBehavior.Stop;
            }
            return HitTestResultBehavior.Continue;
        }

MouseMove事件中,移动标注

        private void MainMap_MouseMove(object sender, MouseEventArgs e)
        {
            if (checkMoveFlag.IsChecked == true &&
                e.LeftButton == MouseButtonState.Pressed
                && _currentElement != null)
            {
                //获取坐标
                Point pt = e.GetPosition(MainMap);
                //转换成地理坐标
                PointLatLng point = MainMap.FromLocalToLatLng((int)pt.X, (int)pt.Y);
                _currentElement.Position = point;
            }
        }

后记:

 winform和WPF是开发桌面程序的两大框架。其中WPF是最新框架,具有很多颠覆性的概念。好多人感觉WPF的概念难以理解,同时感觉到GMap.net对WPF的封装也不够好,使用起来不如winform版好用。WPF版的GMap.net相比与winform版,确实省略了一些功能。这是因为WPF本身就很强大灵活,GMap.net再加上这些功能就多此一举。“”标注检测”就是一例,winform版有直接检测标注的回调函数,WPF版就省略了。WPF是可以通过视觉树HitTest函数来检查,这种检测方法更灵活。

posted @ 2018-11-20 20:37 源之缘 阅读(...) 评论(...) 编辑 收藏