如何较为方便的在GMap.Net中实现车辆运行轨迹

一、简单的思路

  要实现车辆运行轨迹,我们可能需要一个定时触发的机制用来更新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);
}

 

posted @ 2017-07-26 11:33  纳边  阅读(6217)  评论(1编辑  收藏  举报