一、簡單的思路
要實現車輛運行軌跡,我們可能需要一個定時觸發的機制用來更新Marker的位置,除了位置移動,我們可能還需要動態改變車輛的方向,如下圖:
首先,位置移動是最簡單的,關鍵是方向的動態改變如何實現,稍作觀察即可看出,汽車的方向總是和路線的切點平行,看來我們得寫個方法用來求路線上任意點的切線了。可能對於有些大神來說這也並不棘手,無非是花點時間寫個算法而已,但我覺得僅憑我自己的本事可能做不到,所以我打算借助現有的代碼庫來實現上述的功能。在wpf中,路徑動畫是很常用的,而它正好和這里的需求相符合,我們是不是能利用它來實現上述的功能呢?
二、路徑動畫demo
博客園有很多關於wpf路徑動畫的隨筆,如果你還未曾了解過,可以看這里的一篇:http://www.cnblogs.com/zhouyinhui/archive/2007/07/31/837893.html,里面很詳細的介紹了路徑動畫的使用方法,並且附帶了demo可供下載,我建議先看完這篇隨筆后再往下閱讀。為了方便的在動畫執行過程中獲得運動對象的位置坐標和旋轉角度,我選擇了這篇隨筆中介紹的DoubleAnimationUsingPath的方法,我們需要在此基礎上訂閱任意一個Transform實例的Changed事件,以便車輛在改變位置時能通知我們:
var translate = new TranslateTransform(); var rotate = new RotateTransform(); var group = new TransformGroup(); translate.Changed += (s, e) => {
//在這里獲取小車的位置坐標和旋轉角度 };
上面的代碼中,我給TranslateTransfor的實例訂閱了事件,現在,小車的位置就是new Point(translate.X, translate.Y),小車的旋轉角度就是rotate.Angle,好了,該要的東西我們都有了,下面就要在GMap中實現了。
三、自定義Marker
首先,你看到這篇隨筆就代表你對GMap還是有一定了解的,那么自然也知道Marker是個啥,不知道的可以利用搜索引擎了解一下,或者參考這篇隨筆:http://www.cnblogs.com/luxiaoxun/p/3475355.html,我在這里就不介紹了。在地圖上的小車其實就是個我們自定義的Marker,我們姑且稱為CarGMapMarker,在CarGMapMarker內部我們需要維護一個Canvas子類(因為繼承了Canvas),這個Canvas是Path的容器,然后我們還需要一個Border來當作運動的物體,其實這些過程都是為了模擬http://www.cnblogs.com/zhouyinhui/archive/2007/07/31/837893.html中創建的情形,接着我們還需要一個事件public event EventHandler<Tuple<double, Point>> PositionChanged,用來通知我們自定義的Marker:喂!我內部維護的那個Border位置和角度改變了,他們分別是xxxxxxxx。而通知的代碼就寫在二中談到的Changed事件觸發方法中:
var translate = new TranslateTransform(); var rotate = new RotateTransform(); var group = new TransformGroup(); translate.Changed += (s, e) => { OnPositionChanged(new Tuple<double, Point>(rotate.Angle, new Point(translate.X, translate.Y))); };
然后我們只要在自定義Marker中訂閱這個Canvas子類的PositionChanged事件,並從e中獲取一個元組,元組的Item1就是角度,Item2就是坐標,我們可以利用角度改變Marker圖片的方向,利用坐標改變MarkerPosition的值。
不過在此之前我們Canvas子類中的Path還沒有給它的Data屬性賦值,生成這個Data其實很簡單,就是把小車需要經過的關鍵點用線連起來就可以了,直接上方法:
public void SetPoints(List<Point> list) { var geometry = new PathGeometry(); var fi = new PathFigure {StartPoint = list.First() }; foreach (var item in list.Skip(1)) { fi.Segments.Add(new LineSegment(item, false)); } geometry.Figures.Add(fi); }
這里要注意的是需要把list的第一個坐標賦值給PathFigure的StartPoint屬性,剩余的坐標再一一相連接。最后,只要把這個geometry賦值給Canvas子類中Path的Data屬性即可,你可以用方法賦值,也可以在Canvas子類中寫個屬性賦值,隨你,我這里使用了后者。
四、圖片處理
下面要說的是旋轉小車的圖片,Marker中的圖片用的是Bitmap,旋轉Bitmap的方法網上有很多,我們有時候可以奉行拿來主義,搜一個拿來用吧。要注意的是,小車的初始狀態車頭是要朝上的,因為朝上就是0度,和坐標系吻合。
除此之外還有一個坑需要注意,在GMap中Marker默認都是處於目標點上方的,而不是中心點,可以用以下的圖片來理解:
如圖,定位點最低點會在路線上,而不是定位點的中心在路線上,如果直接把定位點的圖片替換成汽車會如何?會這樣子:
你問為什么車沒有旋轉?好的,那么我就讓她旋轉一下,和該點的切線平行好了,Bitmap旋轉是圍繞中心點旋轉的,那么旋轉后效果就是這樣子的:
雖然和切線平行了,但是小車完全脫離了路線,怎么辦?往下移唄!移多少?高度的一半!光是這樣還不夠,我們還需要保證小車圖片的高度和寬度都要相等,即要是個正方形才可以,至於為什么,博友們可以自己想想。
最后的效果如下:
最后的最后,細心的博友可能發現了,小車在開始的位置是偏離的,這就涉及到了GMap內部的bitmap繪制機制,經過測試發現,需要使用以下的代碼來稍作修正:
public override void OnRender(Graphics g) { var bitmap = PictureHelper.RotateImage(_bitmap, _angle); var offsetX = LocalPosition.X-((LocalPosition.X - 30)+bitmap.Width/2); var offsetY = LocalPosition.Y - ((LocalPosition.Y - 60) + bitmap.Height / 2); g.DrawImageUnscaled(bitmap, LocalPosition.X + offsetX, LocalPosition.Y+offsetY); }