前言
在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函數來檢查,這種檢測方法更靈活。