2011年4月25日星期一

ArcGIS API for Windows Phone开发实例(6):对超市信息进行时间查询

  本文内容:时态数据的概念,ArcGIS API中符号的运用
  本文来完成按时间对超市营业额信息查看的功能。拖动屏幕上方的滑块,当前日期会随着变化,而地图上显示的内容则是当前日期(某一天)每个店面的营业额状况:符号大小直观的表示了每个店面营业额的大体情况,当地图放大到一定程度时,显示出具体的数字,代表该店面当天营业额的多少。选中“连续查看”后,则可以以动画的形式对一段时间内的营业额进行连续播放。

clip_image002

  ArcGIS 10中强调了时态数据的概念。可以通过矢量或影像属性表中一个(某一个时刻)或两个字段(某一时间段),来表示该行数据发生或持续的时间。时态数据通常分为两类,一类是随着时间的变化,要素的图形或位置发生了变化(shape字段的变化),比如随着时间的变化,台风中心位置(点)进行了移动,或者火灾中的过火面积(面)发生了改变;另一类是随着时间的变化,要素的属性值发生了变化(非shape字段的变化),比如本例中每个超市的营业额。ArcGIS中,不同时刻或时段的数据用表中不同的行来存储,为了减少数据冗余,通常也采取多表关联的形式来管理时态数据
  本例中为了数据存储简单起见,将每天的营业额按列存储,也就是每天的营业额存储在了每个超市属性的不同字段中。这样我们就需要自己通过代码来完成时间展示的功能。当然也可以用ArcGIS提供的Transpose Fields工具,轻松将数据转换为ArcGIS能识别的分行存储的时态格式,从而利用API中Map/FeatureLayer的TimeExtent属性来完成我们的功能。
  先来看一下我们的TimeQuery工具初始化完成的工作:

   1: private bool _isActivated;
   2:         DispatcherTimer _dTimer;//use to control autoplay
   3:         private Border _Border;//stackpanel used to hold the slider and textblock
   4:         private CheckBox _chkboxAutoPlay;//auto paly animation?
   5:         private Slider _slider;//time slider
   6:         private TextBlock _textblock;//date textblock
   7:         private FeatureLayer _FLayer;//business layer
   8:         private GraphicsLayer _GLayerSymbol;//do nothing with Business FeatureLayer, using another graphicslayer to display symobls
   9:         private GraphicsLayer _GLayerText;//becuase the binding didn't work well in wp7 api, so using another textsymbol in a graphicslayer
  10:         //instead of a custom markersymbol with a text in it(using binding). 
  11:         private double _salesmin, _salesmax;//use to determine symbol size
  12:         private enum SymbolSize
  13:         {
  14:             small=20,
  15:             middle=40,
  16:             large=60
  17:         }
  18:         public Map map1 { get; set; }
  19:         public bool IsActivated
  20:         {
  21:             get { return _isActivated; }
  22:             set
  23:             {
  24:                 if (_isActivated != value)
  25:                 {
  26:                     _isActivated = value;
  27:                     if (value)
  28:                     {                        
  29:                         SlidePanel(Visibility.Visible, TimeSpan.FromMilliseconds(300));
  30:                         _FLayer.Visible = false;
  31:                         map1.Layers.Add(_GLayerSymbol);
  32:                         map1.Layers.Add(_GLayerText);
  33:                         InitSymbols();
  34:                         _chkboxAutoPlay.Visibility = Visibility.Visible;
  35:                         _slider.ValueChanged += _slider_ValueChanged;
  36:                         map1.ExtentChanged += map1_ExtentChanged;
  37:                     }
  38:                     else
  39:                     {
  40:                         SlidePanel(Visibility.Collapsed, TimeSpan.FromMilliseconds(300));
  41:                         _FLayer.Visible = true;
  42:                         map1.Layers.Remove(_GLayerText);
  43:                         map1.Layers.Remove(_GLayerSymbol);
  44:                         UnInitSymbols();
  45:                         _chkboxAutoPlay.Visibility = Visibility.Collapsed;
  46:                         map1.ExtentChanged -= map1_ExtentChanged;
  47:                         
  48:                         _slider.ValueChanged -= _slider_ValueChanged;
  49:                         _chkboxAutoPlay.IsChecked = false;
  50:                         _dTimer.Stop();
  51:                     }
  52:                 }
  53:             }
  54:         }    
  55:  
  56: /// <summary>
  57:         /// display or hide the time panel with a slide animation
  58:         /// </summary>
  59:         /// <param name="visible"></param>
  60:         /// <param name="duration"></param>
  61:         private void SlidePanel(Visibility visible, TimeSpan duration)
  62:         {
  63:             DoubleAnimation da = new DoubleAnimation();
  64:             da.Duration = duration;
  65:             if (visible == Visibility.Visible)
  66:             {
  67:                 //_Border.Visibility = Visibility.Visible;
  68:                 da.From = 0;
  69:                 da.To = 100;
  70:             }
  71:             else
  72:             {
  73:                 //_Border.Visibility = Visibility.Collapsed;
  74:                 da.From = 100;
  75:                 da.To = 0;
  76:             }
  77:             Storyboard sb = new Storyboard();
  78:             Storyboard.SetTarget(da, _Border);
  79:             Storyboard.SetTargetProperty(da, new PropertyPath("Height"));
  80:             sb.Children.Add(da);
  81:             sb.Begin();
  82:         }
  83:  
  84: private void InitSymbols()
  85:         {
  86:             foreach (Graphic g in _FLayer.Graphics)
  87:             {
  88:                 //determin symbol size
  89:                 double size = -1;
  90:                 if ((double)g.Attributes["D1101"] <= (_salesmax + _salesmin)/2 * 1 / 3)
  91:                     size = (double)SymbolSize.small;
  92:                 else if ((double)g.Attributes["D1101"] > (_salesmax + _salesmin) / 2 * 1 / 3 && (double)g.Attributes["D1101"] <= (_salesmax + _salesmin) / 2 * 2 / 3)
  93:                     size = (double)SymbolSize.middle;
  94:                 else
  95:                     size = (double)SymbolSize.large;
  96:  
  97:                 //change Businesslayer symbol to custom symbols and display in _GLayerSymbol for each store
  98:                 Graphic gsymbol = new Graphic()
  99:                 {
 100:                     Geometry = g.Geometry,
 101:                     Symbol = new PictureMarkerSymbol()
 102:                     {
 103:                         Source = new BitmapImage(new Uri("../Images/Dollar.png", UriKind.Relative)),
 104:                     }
 105:                 };
 106:                 
 107:                 (gsymbol.Symbol as PictureMarkerSymbol).Height = (gsymbol.Symbol as PictureMarkerSymbol).Width = size;
 108:                 (gsymbol.Symbol as PictureMarkerSymbol).OffsetX = (gsymbol.Symbol as PictureMarkerSymbol).OffsetY = size / 2;
 109:                 g.Attributes.Add("symbol", gsymbol);
 110:  
 111:                 //add a graphic with textsymbol, which display sales, to _GLayerText for each store
 112:                 Graphic gtext = new Graphic()
 113:                 {
 114:                     Geometry = g.Geometry,
 115:                     Symbol = new TextSymbol()
 116:                     {
 117:                         Foreground = new SolidColorBrush(Color.FromArgb(225,255,255,255)),
 118:                         Text = double.Parse(g.Attributes["D1101"].ToString()).ToString("###,###"),
 119:                         OffsetX = -size / 2+15,
 120:                         OffsetY = -size / 2+15,
 121:                         FontSize=30
 122:                     }
 123:                 };
 124:                 gtext.Symbol.ControlTemplate = (App.Current.Resources["LegendTextSymbol"] as TextSymbol).ControlTemplate;
 125:                 g.Attributes.Add("text", gtext);
 126:  
 127:                 _GLayerText.Graphics.Add(gtext);
 128:                 _GLayerSymbol.Graphics.Add(gsymbol); 
 129:             }
 130:             _GLayerText.Visible = false;
 131:         }


  在这个工具类的构造函数中,添加了一些控件,包括一个控制时间的slider,一个自动播放的checkbox,一个显示当前日期的textblock。另外找出了所有店面所有时间范围内营业额的最大值和最小值,这样有助于对我们的营业额进行“归一化”,从而决定地图符号的大小。



   1: public TimeQuery(Map map)
   2:         {
   3:             map1 = map;
   4:             _GLayerText = new GraphicsLayer();
   5:             _GLayerSymbol= new GraphicsLayer();
   6:             _FLayer = map1.Layers["BusinessLayer"] as FeatureLayer; 
   7:             _slider = new Slider()
   8:             {
   9:                 Height=84,
  10:                 Width=360,
  11:                 Minimum=1,
  12:                 Maximum=30,
  13:                 SmallChange=1,
  14:                 LargeChange=5,
  15:                 Margin=new Thickness(0,0,0,-30)
  16:             };
  17:  
  18:             _textblock = new TextBlock()
  19:             {
  20:                 Text = "时间:11月1日",
  21:                 Foreground=new SolidColorBrush(Colors.White),
  22:                 FontSize=20,
  23:                 FontWeight=FontWeights.Bold,
  24:                 Margin=new Thickness(20,0,0,0),
  25:             };
  26:             StackPanel sp = new StackPanel()
  27:             {
  28:                 HorizontalAlignment=HorizontalAlignment.Center,
  29:                 VerticalAlignment=VerticalAlignment.Center,
  30:             };
  31:             sp.Children.Add(_slider);
  32:  
  33:             //auto play box
  34:             _chkboxAutoPlay = new CheckBox()
  35:             {
  36:                 Content = "连续查看",
  37:                 FontSize = 20,
  38:                 FontWeight=FontWeights.Bold,
  39:                 Foreground = new SolidColorBrush(Colors.White),
  40:                 Visibility = Visibility.Collapsed,
  41:                 Margin = new Thickness(90, -22, 0, 0),
  42:             };
  43:  
  44:             _dTimer = new DispatcherTimer()
  45:             {
  46:                 Interval = TimeSpan.FromMilliseconds(500)
  47:             };
  48:             _dTimer.Tick += (sender, args) =>
  49:             {
  50:                 if (_slider.Value == 30)
  51:                     _slider.Value = 1;
  52:                 else
  53:                     _slider.Value += 1;
  54:             };
  55:  
  56:             _chkboxAutoPlay.Click += (s, a) =>
  57:             {
  58:                 CheckBox chkbox = s as CheckBox;
  59:                 if (chkbox.IsChecked == true)
  60:                 {
  61:                     _dTimer.Start();
  62:                 }
  63:                 else
  64:                 {
  65:                     _dTimer.Stop();
  66:                 }
  67:             };
  68:             StackPanel sp1 = new StackPanel()
  69:             {
  70:                 Orientation = Orientation.Horizontal
  71:             };
  72:             sp1.Children.Add(_textblock);
  73:             sp1.Children.Add(_chkboxAutoPlay);
  74:             sp.Children.Add(sp1);
  75:  
  76:             _Border = new Border()
  77:             {
  78:                 Background=new SolidColorBrush(Color.FromArgb(225,0,0,0)),
  79:                 Width=400,
  80:                 Height=0,
  81:                 CornerRadius= new CornerRadius(10),
  82:                 BorderBrush=new SolidColorBrush(Colors.White),
  83:                 BorderThickness=new Thickness(5),
  84:                 //Visibility=Visibility.Collapsed,
  85:                 VerticalAlignment=VerticalAlignment.Top,
  86:             };
  87:             _Border.Child = sp;
  88:  
  89:             (map1.Parent as Grid).Children.Add(_Border);
  90:  
  91:  
  92:             _salesmin = _salesmax = 0;
  93:             //find day minsales and maxsales in all graphics and all days
  94:             for (int i = 1; i <= 30; i++)
  95:             {
  96:                 string strDay = string.Empty;
  97:                 if (i < 10)
  98:                     strDay = "0" + i.ToString();
  99:                 else
 100:                     strDay = i.ToString();
 101:                 double fieldmax = (from graphic in _FLayer.Graphics
 102:                                    select graphic).Max(a => (double)a.Attributes["D11" + strDay]);
 103:                 _salesmax = _salesmax > fieldmax ? _salesmax : fieldmax;
 104:                 double fieldmin = (from graphic in _FLayer.Graphics
 105:                                    select graphic).Min(a => (double)a.Attributes["D11" + strDay]);
 106:                 _salesmin = _salesmin < fieldmin ? _salesmin : fieldmin;
 107:             }
 108:         }


  当控制时间的slider发生变化时,我们就根据当前日期的营业额,计算出每个超市店面符号的大小,从而使用新的符号来显示。



   1: void _slider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
   2:         {
   3:             int day = (int)e.NewValue;
   4:             //change text
   5:             _textblock.Text = string.Format("时º¡À间?:11月?{0}日¨?", day.ToString());
   6:  
   7:             ChangeSymbolAndText(day);
   8:         }
   9:  
  10: /// <summary>
  11:         /// change symbols and text according to current day
  12:         /// </summary>
  13:         /// <param name="day"></param>
  14:         private void ChangeSymbolAndText(int day)
  15:         {
  16:             string strDay = string.Empty;
  17:             if (day < 10)
  18:                 strDay = "D11"+"0" + day.ToString();
  19:             else
  20:                 strDay = "D11" + day.ToString();
  21:             
  22:             foreach (Graphic g in _FLayer.Graphics)
  23:             {
  24:                 //determin symbol size
  25:                 double size = -1;
  26:                 if ((double)g.Attributes[strDay] <= (_salesmax - _salesmin) * 1 / 3)
  27:                     size = (double)SymbolSize.small;
  28:                 else if ((double)g.Attributes[strDay] > (_salesmax - _salesmin) * 1 / 3 && (double)g.Attributes[strDay] <= (_salesmax - _salesmin) * 2 / 3)
  29:                     size = (double)SymbolSize.middle;
  30:                 else
  31:                     size = (double)SymbolSize.large;
  32:  
  33:                 PictureMarkerSymbol symbol = (g.Attributes["symbol"] as Graphic).Symbol as PictureMarkerSymbol;
  34:                 symbol.Height = symbol.Width = size;
  35:                 symbol.OffsetX = symbol.OffsetY = size / 2;
  36:  
  37:                 TextSymbol text = (g.Attributes["text"] as Graphic).Symbol as TextSymbol;
  38:                 text.Text = double.Parse(g.Attributes[strDay].ToString()).ToString("###,###");
  39:                 text.OffsetX = -size / 2 + 10;
  40:                 text.OffsetY = -size / 2 + 10;
  41:                 //text.FontSize = size == (double)SymbolSize.small ? size : size - 10;
  42:             }
  43:         }


  最后,还要控制营业额数字的显示范围。当比例尺较小时,超市图标分布较为密集,具体营业额数据不予显示;当比例尺较大时,才显示每个超市的具体营业额数字。



   1: void map1_ExtentChanged(object sender, ExtentEventArgs e)
   2:         {
   3:             if (map1.Resolution > 50.2185141425366)//38
   4:                 _GLayerText.Visible = false;
   5:             else
   6:                 _GLayerText.Visible = true;
   7:         }


  至此,我们的第三个功能也完成了。在这里请各位朋友思考一下,如果使用ArcGIS格式的时态数据,我们按时间查看超市营业额的功能该怎么做?相较于本文中的方法,各自的优缺点是什么?

  参考资料:
  ArcGIS中的时态数据:http://help.arcgis.com/en/arcgisdesktop/10.0/help/index.html#/What_is_temporal_data/005z00000001000000/

没有评论:

发表评论