本博客编写时的软件环境为VS 2010sp1+ArcGIS API for Silverlight 3.0
需求:网络分析之后要求把分析出的最佳路径结果以动画形式实现轨迹回放,并要求回放过程中实现“车头拐角”。
我们可能会想到使用timer来实现,每个一段时间来实现“小车”的坐标更新,但是你会发现这样会出现闪动效果,亮点之间不是平滑过渡的,这样用户体验不是太好。参考网络上给出了一个更好的方法,把他整理成了博客分享一下,并添加了车头转向的功能,我们知道在Silverlight中有个故事版方便我们实现动画,并且动画都是连续性的。不过如果要使用动画控制 小车的位置的话,必须要求小车的坐标要为依赖属性。所以这个我们要自己扩展
首先新建 一个名字为Flag的UserControl 作为小车,在这里我添加一个箭头的图片(一个水平向右的箭头)
XAML如下
<Grid x:Name="LayoutRoot" Background="White"> <Image x:Name="Image1" Height="40" Width="40" Source="/Car.png" Stretch="Uniform"> <Image.RenderTransform> <TransformGroup> <RotateTransform x:Name="RotateItemCanvas" CenterX="20" CenterY="20"/> </TransformGroup> </Image.RenderTransform> </Image> </Grid>
上面的代码表示,我添加了一个长宽各位40的图片,并且把图片的中心作为旋转的中心。
我们定义的这个控件的功能就是一个ElementLayer中的一个element 与地图控件中的一条线来绑定,通过调用Start ,Stop,Pause,Resume来控制动画,这里需要定义两个依赖属性,这也是我们选择使用Elment ,而不是Graphic 来表示小车的原因,因为Graphic的坐标不具有对应的依赖属性。不过控制Element的位置我们可以通过自己定义两个依赖属性Xproperty和Yproperty。然后对应的X,Y来与Element的位置绑定,通过ElementLayer的SetEnvelope来设置小车的位置,如果X,Y有变化的话,在Set方法调用了自定义的ResetEnvelope 来重新设定小车的位置,我们同时又定义了CalulateXYAnagle 来根据某一段轨迹的方向来计算小车车头要旋转的的方向。同时可以通过 Interval 属性来控制每个轨迹线段用的时间(动态计算可以设置小车移动的速度);
public partial class Flag : UserControl { private Map _PMap = null; private int count = 0; private ESRI.ArcGIS.Client.Geometry.PointCollection PCol = null; private Storyboard sb = new Storyboard(); DoubleAnimation dba; DoubleAnimation dba1; /// <summary> /// 执行结果路线 /// </summary> private Graphic _Route = null; public Graphic Route { get { return _Route; } set { _Route = value; } } public Flag() { InitializeComponent(); } public string ImageSource { set { Image1.Source = new BitmapImage(new Uri(value, UriKind.Relative)); } } public Map BindMap { set { _PMap = value; } get { return _PMap; } } public static readonly DependencyProperty Xproperty = DependencyProperty.Register("X", typeof(double), typeof(Flag), new PropertyMetadata(OnXChanged)); public double X { get { return (double)base.GetValue(Xproperty); } set { base.SetValue(Xproperty, value); ResetEnvelop(); } } private static void OnXChanged(object sender, DependencyPropertyChangedEventArgs e) { (sender as Flag).X = (double)e.NewValue; } public static readonly DependencyProperty Yproperty = DependencyProperty.Register("Y", typeof(double), typeof(Flag), new PropertyMetadata(OnYChanged)); public double Y { get { return (double)base.GetValue(Yproperty); } set { base.SetValue(Yproperty, value); ResetEnvelop(); } } private static void OnYChanged(object sender, DependencyPropertyChangedEventArgs e) { (sender as Flag).Y = (double)e.NewValue; } private ElementLayer _bindLayer = null; public ElementLayer bindLayer { get { return _bindLayer; } set { _bindLayer = value; } } private void ResetEnvelop() { Flag ele = (Flag)_bindLayer.Children[0]; ElementLayer.SetEnvelope(ele, new ESRI.ArcGIS.Client.Geometry.Envelope(X, Y, X, Y)); if (_PMap != null) { ESRI.ArcGIS.Client.Geometry.Polygon gon = new ESRI.ArcGIS.Client.Geometry.Polygon(); ESRI.ArcGIS.Client.Geometry.PointCollection con = new ESRI.ArcGIS.Client.Geometry.PointCollection(); } } /// <summary> /// 移动时间间隔 默认1 /// </summary> /// private double _Interval = 1; public double Interval { get { return _Interval; } set { _Interval = value; } } public void Start() { if (_PMap != null) { _PMap.ZoomTo(_Route.Geometry); } if (_bindLayer.Children.Count > 0) _bindLayer.Children.RemoveAt(0); _bindLayer.Children.Add(this); ESRI.ArcGIS.Client.Geometry.Polyline line = _Route.Geometry as ESRI.ArcGIS.Client.Geometry.Polyline; PCol = line.Paths[0]; MapPoint pt1 = PCol[0]; MapPoint pt2 = PCol[1]; double angle = CalulateXYAnagle(pt1.X, pt1.Y, pt2.X, pt2.Y); RotateItemCanvas.Angle = angle; ElementLayer.SetEnvelope(this, pt1.Extent); if (dba == null) { dba = new DoubleAnimation(); Storyboard.SetTarget(dba, this); Storyboard.SetTargetProperty(dba, new PropertyPath("X")); sb.Children.Add(dba); dba1 = new DoubleAnimation(); Storyboard.SetTarget(dba1, this); Storyboard.SetTargetProperty(dba1, new PropertyPath("Y")); sb.Children.Add(dba1); } sb.Completed += new EventHandler(sb_Completed); dba.From = pt1.X; dba.To = pt2.X; dba.Duration = new Duration(TimeSpan.FromSeconds(_Interval)); dba1.From = pt1.Y; dba1.To = pt2.Y; dba1.Duration = new Duration(TimeSpan.FromSeconds(_Interval)); sb.Begin(); } public static double CalulateXYAnagle(double startx, double starty, double endx, double endy) { double tan = Math.Atan(Math.Abs((endy - starty) / (endx - startx))) * 180 / Math.PI; if (endx > startx && endy > starty)//第一象限 { return -tan; } else if (endx > startx && endy < starty)//第二象限 { return tan; } else if (endx < startx && endy > starty)//第三象限 { return tan - 180; } else { return 180 - tan; } } private void sb_Completed(object sender, EventArgs e) { if (count < PCol.Count - 2) { count++; MapPoint pt1 = PCol[count]; MapPoint pt2 = PCol[count + 1]; DoubleAnimation db = (DoubleAnimation)(sender as Storyboard).Children[0]; db.From = pt1.X; db.To = pt2.X; double angle = CalulateXYAnagle(pt1.X, pt1.Y, pt2.X, pt2.Y); RotateItemCanvas.Angle = angle; DoubleAnimation db1 = (DoubleAnimation)(sender as Storyboard).Children[1]; db1.From = pt1.Y; db1.To = pt2.Y; sb.Begin(); } else { } } public void Stop() { sb.Stop(); } public void Pause() { sb.Pause(); } public void Resume() { sb.Resume(); } }
以上 是一个封装好的控件,下面再代码里使用如下,需要一个Map,一个ElementLayer,一条多线。
ElementLayer elelay = MyMap.Layers["MoveCarLayer"] as ElementLayer; ESRI.ArcGIS.Client.Geometry.Polyline Pline = lastRoute.Geometry as ESRI.ArcGIS.Client.Geometry.Polyline; //自定义元素 Flag mele = new Flag(); mele.BindMap = MyMap; mele.bindLayer = elelay; mele.Interval = 1; //时间你可以动态设置,根据线的长度除以速度 mele.Route = lastRoute; mele.Start();
当然也可以在播放过程中进行暂停,停止,恢复动画。
最后附上一个Demo,我通过调用网络分析服务,来分析出两点间最佳路径,然后通过动画形式来,由于用的服务是网络上的,大家下载后即可运行。
运行之后 在地图上点击两点结果如下,,
原文链接:http://blog.csdn.net/arcgisserver_book/article/details/8125448