一、简单的思路
要实现车辆运行轨迹,我们可能需要一个定时触发的机制用来更新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旋转是围绕中心点旋转的,那么旋转后效果就是这样子的:
虽然和切线平行了,但是小车完全脱离了路线,怎么办?往下移呗!移多少?高度的一半!光是这样还不够,我们还需要保证小车图片的高度和宽度都要相等,即要是个正方形才可以,至于为什么,博友们可以自己想想。
最后的效果如下: