使用 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函数来检查,这种检测方法更灵活。