显示标签为“Windows Phone”的博文。显示所有博文
显示标签为“Windows Phone”的博文。显示所有博文

2012年6月1日星期五

2011年12月19日星期一

ArcGIS移动客户端中可以自动离线的底图图层

  我们都知道,在使用ArcGIS移动客户端(已更名为ArcGIS Runtime SDK for iOS/Windows Phone/Android)API进行开发时,一般对于底图数据,需要用ArcGISTiledMapServiceLayer这个类来加载,指向一个在线的缓存地图服务。而对于移动GIS应用,通常我们又有非常强烈的离线需求。目前来说,ArcGIS移动客户端底图离线有两种实现办法:自定义图层加载离线数据或等待新版本api来加载10.1的TilePackage底图离线包(iOS API已经实现)。
  这次我们来讨论另一种可以让底图离线的办法。在手机上使用过离线地图程序的朋友都知道,它们一般都会提供自动离线的功能,即当我们连接到互联网,浏览在线地图时,程序会自动将浏览过的数据缓存到本地,以便下次没有网络环境时可离线使用。新版本的Goolge Maps移动版,Android上的RMaps,OruxMaps等程序都有类似的功能。
  那么如何为ArcGIS的移动程序实现类似的功能呢?因为这个图层既要能够加载在线的地图服务,又要能够自动离线,所以我们选择创建一个派生自ArcGISTiledMapServiceLayer的类。当有网络连接时,我们浏览地图的过程中,将切片自动存储到本地,没有网络连接时就可以浏览这些切片了。而且可以给用户提供若干选项,比如是否允许自动下载缓存切片,是否允许自动更新已有的缓存切片等。
  请注意本文讨论内容与之前讨论的自定义图层加载离线数据不同,前者具有自动下载缓存切片的功能,下载后没有网络连接的情况下会自动变成离线图层加载离线数据,有网络连接的情况下可当做正常的在线图层使用;后者只能当做离线图层使用,不能自动下载缓存切片。
  这里我以Windows Phone为例,写好了一个扩展类:OfflineableTiledMapServiceLayer(下载地址见最后)。下面对它进行一些说明。
功能:
  OfflineableTiledMapServiceLayer是一个继承自ArcGISTiledMapServiceLayer的自定义类,在线浏览地图的过程中,会自动将缓存切片保存在Sqlite数据库文件中(存储在应用程序的IsolatedStorage空间内),无需人工干预;这样在没有网络连接的情况下,就自动加载先前保存过的切片,作为离线图层使用(程序代码无需做任何修改)。

如何使用:
  与ArcGISTiledMapServiceLayer用法一致,只需额外设置一些参数即可。
<esri:Map x:Name="map1">
    <my:OfflineableTiledMapServiceLayer Url="http://services.arcgisonline.com/ArcGIS/rest/services/Ocean_Basemap/MapServer" EnableOffline="True"  SaveOfflineTiles="True" SaveTilesMode="SaveOnly"
DeleteSavedOfflineTiles="False" LoadOfflineTileFirst="True" />
</esri:Map>

工作流程:
  除了ArcGISTiledMapServiceLayer已有的功能外,OfflineableTiledMapServiceLayer可在浏览地图的过程中,自动将缓存切片保存到Sqlite数据库中。当下次加载该服务时(以URL地址识别),就可从离线的数据库中加载切片数据(没有网络连接的情况下)。
  OfflineableTiledMapServiceLayer目前有以下5个属性可设置:

  • EnableOffline: 默认为True。当设置为True时,OfflineableTiledMapServiceLayer具有自动离线的能力;当设置成False时,OfflineableTiledMapServiceLayer完全和ArcGISTiledMapServiceLayer一样。
  • SaveOfflineTiles: 默认为True。当设置为True时,OfflineableTiledMapServiceLayer会将浏览过的缓存切片保存到本地的Sqlite数据库中(存储在应用程序的IsolatedStorage空间);当设置为False时,OfflineableTiledMapServiceLayer不会保存任何缓存切片。只有当LoadOfflineTileFirst==false时才会生效。
  • LoadOfflineTileFirst: 默认为False。当设置为True时,OfflineableTiledMapServiceLayer会优先从本地Sqlite数据库中加载缓存切片(即使有Internet连接),如果处于离线状态,则会直接从本地Sqlite数据库中加载缓存切片(如果之前没有保存过任何切片,则会抛出异常);当设置为False时,则会优先从在线服务中加载缓存切片,如果处于离线状态,则只能从本地Sqlite中加载数据。
  • SaveTilesMode: 默认为'SaveOnly'。当设置为'SaveOnly'时,OfflineableTiledMapServiceLayer只会存储新的缓存切片(Sqlite数据库中没有的);当设置为'SaveOrUpdate'时,OfflineableTiledMapServiceLayer会存储新的缓存切片,并且更新已有的缓存切片。当在线的缓存地图服务内容更新时,此选项比较有用。只有当LoadOfflineTileFirst==false && SaveOfflineTiles==true时有效。
  • DeleteSavedOfflineTiles: 默认为False。当设置成True时,会首先删除Sqlite数据库中已有的本图层数据,之后重新初始化本图层。只有当EnableOffline==True时有效。


Sqlite文件结构:
  Sqlite在移动设备上具有广泛的应用基础,iOS和Android对其均提供原生支持。OfflineableTiledMapServiceLayer使用Sqlite Client for Windows Phone来读写Sqlite数据库。可以使用Windows Phone 7 Isolated Storage Explorer或者Isolated Storage Explorer Tool将Sqlite文件从Windows Phone的IsolatedStorage中导出,以供其它移动程序使用,比如ArcGIS Runtime SDK for iOS/Android。
  OfflineableTiledMapServiceLayer创建的Sqlite数据库名称为"OfflineTiles.db",它的内容由一张或多张表组成。

  • 'MapServices'表:此表是OfflineableTiledMapServiceLayer必须使用的。具有四个字段:'url'(text), 'spatialreference'(text), 'fullextent'(text), 'tileinfo'(text)。存储在Sqlite中不同的OfflineableTiledMapServiceLayer由其Url属性区分(假设不同的Url代表不同的缓存地图服务)。另外三个字段分别以JSON格式存储了缓存服务对应的信息,这些信息是在离线状态下初始化图层所需要的。创建该表的SQL语句: CREATE TABLE "MapServices" ("url" TEXT PRIMARY KEY  NOT NULL  UNIQUE , "spatialreference" TEXT NOT NULL , "fullextent" TEXT NOT NULL , "tileinfo" TEXT NOT NULL )
  • 其它表:如果Sqlite中存储过任何OfflineableTiledMapServiceLayer缓存图片,则除了上述MapServices表外还会有其它表。其余每张表以该缓存地图服务的Url字符串命名。这些表包含有4个字段:'level'(integer), 'row'(integer), 'column'(integer), 'tile'(blob)。一目了然。创建该表的SQL语句:CREATE TABLE "HereIsYourServiceURL" ("level" INTEGER NOT NULL , "row" INTEGER NOT NULL , "column" INTEGER NOT NULL , "tile" BLOB NOT NULL ), 创建索引的SQL语句: CREATE UNIQUE INDEX 'idx_ HereIsYourServiceURL ' ON ' HereIsYourServiceURL ' ('level' ASC, 'row' ASC, 'column' ASC)


OfflineableTiledMapServiceLayer的下载地址(包括源码,示例程序,示例Sqlite离线文件):http://www.arcgis.com/home/item.html?id=d2b40d7f553947a2b575556b057f5dcf

2011年6月24日星期五

2011 Esri中国开发者大会讲座:ArcGIS API for Windows Phone进阶视频

内容介绍:
  • 快速入门
  • 界面设计
  • 触摸 & 手势
  • 应用程序执行模型(Execution Model)
  • 位置服务
  • 离线应用(全离线演示从1小时17分钟开始)

2011年6月11日星期六

Windows Phone 7 解锁/破解/越狱步骤 NODO版本有效

  为什么要解锁windows phone机器?不解锁只能通过zune marketplace来下载和安装市场上的软件,解锁后才能用windows phone developer tools里的application deployment工具将自己的.xap程序部署到手机上。当然只是临时做测试用,有条件还是需要注册微软每年99美元的开发者账号,来绑定自己的机器以部署程序吧,大家懂的。
  现在国内市面上还没有windows phone行货,有消息称最早在今年8月份行货机器有望上市。目前windows phone有4个版本,去年发布时最初的第一个版本7.0.7004.0,今年2月份的第一个更新版本7.0.7008.0,4月份的NODO版本(7.0.7390.0),和最新的7.0.7392.0安全更新版本。这两天拿到了htc的hd7机器做测试,是未解锁的NODO版本,无奈中文资料比较少,只好上神坛xda找答案。
  目前wp的解锁工具只有一个,就是http://www.chevronwp7.com/放出的ChevronWP7,但是该工具只适用于7004和7008版本,7390以上版本无效,必须刷回之前的版本才能解锁。解锁之后需要额外的步骤,保证zune同步时机器不会重新被锁,并且升级到NODO版本后,解锁依然有效。现将完整步骤整理如下(主要参考http://forum.xda-developers.com/showthread.php?t=1043000此贴中TechJunkiesCA的回复)。

一、将7390版本刷回最初的7004版本(如果你是7004或7008版本,可跳过此步骤)

  按照http://forum.xda-developers.com/showthread.php?t=876451这个帖子中的说明,首先确定手机的原始运营商:

In order to identify your HD 7 phone ROM version see in your phone settings>about>more information:
Firmware revision number: 2250.09.15401.728
Your phone ROM version will be: 1.54.728.01
The characterisric value (CV) is: 728
Your Operator name and CIDs i.e.: hTC_Asia_SEA operator, with CIDs HTC__044, HTC_621, HTC__622, HTC__038 can be found with this CV from following classification:
1. 666 : BellMobilty operator, with CID BM___001
2. 707: hTC_Asia operator, with CIDs HTC__044, HTC_621, HTC__622, HTC__038
3. 728: hTC_Asia_SEA operator, with CIDs HTC__044, HTC_621, HTC__622, HTC__038
4. 401: HTC_Europe operator, with CIDs HTC__001, HTC__203, HTC__102, HTC__032, HTC__405, HTC__304
5. 207: O2_DE, with CID O2___102
6. 206: O2_UK, with CID O2___001
7. 841: Telstra operator, with CID TELST001
8. 901: TIM operator, with CID TIM__401
9. 531: TMOUS operator, with CID T-MOB010

  这台机器Firmware最后三位数字是207(对应O2_DE),可得知是销往德国(de)的o2机器。找到帖子下面贴出的对应rom,RUU_Schubert_O2_DE_1.61.207.01_Radio_5.52.09.16_22 .33a.50.10U_by_ansar,下载,解压。
1、长按电源键,关机
2、按住下音量键不放,按电源键,进入bootloader(三色屏)模式(此时可松开下音量键)
3、用usb线将手机与电脑连接
4、等待windows系统自动查找并安装驱动程序
5、驱动安装完成之后,确保手机屏幕上白色屏部分显示“usb”字样(驱动安装之前是“serial”)
image
6、此时可以运行下载好的ROMUpdateUtility.exe(ruu工具),严格按照屏幕提示操作
  如果你之前用过HTC的RUU刷windows mobile机器或者刷android机器,那么最后一步应该非常熟悉。刷机完成之后,检查OS Version应当是7004或者7008。

二、利用ChevronWP7对手机进行解锁

  首先到http://forum.xda-developers.com/showthread.php?t=938106这个帖子中下载必要的工具:Windows Phone 7 Developer Tools,ChevronWP7工具,TCPView for Windows。WPDT对于开发者来说不是问题,肯定已经安装了(否则你需要先安装vs2010,然后安装WPDT)。下载ChevronWP7工具,压缩包中会包含ChevronWP7.exe,ChevronWP7.cer和ChevronWP7.reg三个文件。之后可以开始:
1、在电脑上运行ChevronWP7.reg。会将注册表中[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WindowsPhone\ProxyPorts]的DeviceReg一项值改为dword:000069c5
2、在手机上安装ChevronWP7.cer证书。可将ChevronWP7.cer做为附件发送到自己的邮箱,然后在手机上用浏览器打开该附件,屏幕会显示黑底的小白盾,继续点击“Install”即可
3、确保你机器的443端口没有被占用。运行tcpview.exe工具,查看Local Port一列中显示为https的程序(会占用443端口),全部结束之
4、将手机设置为飞行模式
5、将手机利用usb线与电脑连接,使用Zune软件进行非guest方式同步。如果没有安装Zune(windows自己的itunes),需要提前安装。如果同步方式那块显示的是“Guest Sync”,点击之,运行一下向导,使得你的计算机变成该手机的主要同步电脑(非guest方式同步)
6、以管理员身份运行ChevronWP7.exe进行解锁。确保手机屏幕没有锁定(可在设置中将桌面锁定设为never),处于开始画面;以管理员身份运行ChevronWP7.exe,将两个方框打钩,点击unlock。顺利的话不到5秒钟即可完成,按钮会变成relock。此时最好不要拔出usb线,接着进行下面的第三步。
  至此已完成解锁。可利用WPDT自带的Application Deployment工具,在手机屏幕处于非锁定的状态下,部署任意的.xap文件了。
image

三、确保下次用Zune同步时,手机不会重新被锁

  仅仅利用ChevronWP7.exe解锁后,如果断开与usb线的连接,以后再次插入usb,用Zune同步后,机器可能再次被锁,还需要进行解锁步骤。为防止此情况发生,需要进行以下步骤,确保以后用Zune同步时,机器不会被再次锁定。下面的步骤只适用于htc机器(因为touchxplorer只能在htc机器上运行),其他手机请参考这里。
1、按照http://forum.xda-developers.com/showthread.php?t=913748此贴中的说明,依次下载TouchXplorer,HTC Connection Setup和unlockit(其实是XBMOD.Files.Deployer)三个.xap文件,分别解压并且将它们安装到手机中
2、在手机上运行XBMOD.Files.Deployer。会将CustClear.provxml和restoreCustClear.provxml两个文件解压到My Documents/My Ringtones目录下
3、运行TouchXplorer工具(wp里的资源管理器),将上述两个文件拷贝到\Windows目录下
image
4、运行HTC Connection Setup工具,点击ok,此步骤可选。如果你的手机里有sim卡,需要执行此步,否则可跳过
  至此已经完成了所有的步骤。可以利用Zune同步后将你的手机升级至最新的7392 NODO版本,也不会被再次锁定了:)
image

如何部署大于64M的.xap文件到手机上?

  下载了大名鼎鼎的The Harvest(xap大小86.4m)想尝试一下,但用Application Deployment部署时会提示“Error-设备没有连接”。搜索xda,发现有人说在更新NODO后,大于75M的xap便无法部署了,大家纷纷跟帖表示确有此问题。但万能的xda网友toothfish再次给出解决办法:
1、备份你的xap,以防万一
2、用解压缩工具打开xap文件,将其中体积较大的一些文件,比如Content文件夹解压缩出来,并从压缩包里删除之
3、记下WMAppManifest.xml文件中的productID的值(一个guid)
4、将修改过后的xap(体积应该小于64m)部署到机器上
5、使用Windows Phone Device Manager自带的Touchexperience工具,将第二步中分离出来的那部分文件,拷贝到手机的Applications\Install\"Product ID"\Install目录中,注意维持与原xap文件中同样的目录结构
6、你已经将大xap文件部署成功了~
  其实原理比较简单,做过windows phone开发的朋友都知道,一般会将程序中用到的资源文件,比如一些媒体文件,以Content方式编译到xap中单独的文件夹里;而windows phone程序部署的真正过程也如上所示,只是将xap解压到了特定的文件夹中。因此便可以用上面的办法来移花接木了。
  还要提醒一点,我的机器上已经安装了mango sdk,所以会和Windows Phone Device Manager程序有冲突,因此我用Advanced Explorer来完成拷贝文件到手机中的过程。
image

  无法部署大xap的情况好像只会在机器上既装了mango sdk,并且手机版本为NODO时才会出现,否则(比如将手机还原到7008版本)应当可以直接部署成功。

2011年5月7日星期六

ArcGIS API for Windows Phone开发实例PDF/源码下载

超市数据的shapefile在上面的压缩包中。

ArcGIS API for Windows Phone开发实例(7):利用Geoprocessing分析超市的营业状况

  本文内容:Geoprocessing
  前几节中,我们利用GIS这个工具,对连锁超市店面的营业状况做了充分的展示。这一节中,我们利用GIS特有的Geoprocessing功能,结合超市的营业数据统计,对超市的营业状况做一个比较合理的判断,从而有助于经营者及时调整策略,适应市场的变化。
  我们的大致分析思路是这样的:对于每个超市来说,会有固定的消费群体,一般通常是距离该超市一定范围内的常驻居民,这部分占绝大多数(一次性路过的消费者人数较少,可以忽略不计);首先我们利用GIS这个工具,设法得到每个超市周围一定范围内的常驻人口数,做为该超市的潜在消费群体;然后根据过去一定时间内,这个品牌所有超市的营业额统计,计算出所有超市店面潜在消费群体每人每天的大约消费额度是多少,做为该品牌超市的一个平均竞争力(每人/天购买力是多少);这样就能利用这个平均竞争力数值和某个超市潜在消费人口数,预估出该超市一定时期内大约的营业额收入。如果实际营业额与这个预估相差不大,我们认为该超市营业状况正常;如果营业额统计明显小于这个预估值,则有可能是该超市营业范围内新增了竞争对手的店面,或竞争对手采取了促销措施等手段分流了购买力,从而提醒经营者及时对营业策略做出调整,避免进一步的损失。
  超市店面的营业额统计我们有了,下一步就需要获得每个超市潜在的消费人群数量。我们分两步来完成这项工作:第一步计算出距离某个超市店面一定时间车程的地理范围,作为该超市潜在消费群体的居住区域;第二部查询出该区域内的常驻人口数。
  GIS正是用于处理所有与地理分布相关任务的一个有力工具,Geoprocessing则是它的核心所在。上面的两个步骤我们就可以利用Geoprocessing轻松完成。ArcGIS Server中,将具有一定逻辑相关的一组任务封装成一个处理流程,发布成Geoprocessing Service(简称GP服务),通过Task的形式供客户端调用。客户端只需提供相关的参数后,就可直接得到处理结果,而不需要理会服务器端复杂的处理流程。ArcGIS Online为我们提供了两个现成的Geoprocessing Service:CreateDriveTimePolygons和PopulationSummary。前者用来获得距某点一定时间的车程范围,后者用来获得某范围的常驻人口,刚好符合我们的需求。因此只需调用这两个GP服务,即可得到我们想要的数据,从而完成相关计算工作。
  注:CreateDriveTimePolygons适用于美国境内,在此仅作说明之用;由于数据原因,程序内所使用的北京市内的对应GP服务无法公开。PopulationSummary适用于全世界范围,但结果精度不做保证。关于两个服务的使用限制,请查看相关页面。
  我们依然创建一个工具类,取名Analysis,该类包含两个GeoProcessing Task(Geoprocessor):_gpDriveTime和_gpPopulation,分别用于获取某超市的营业范围和该范围内的常驻人口数。

   1: private Geoprocessor _gpDriveTime = new Geoprocessor(App.Current.Resources["GPDriveTime"] as string);
   2: private Geoprocessor _gpPopulation = new Geoprocessor(App.Current.Resources["GPPopulation"] as string);
   3: Time.ExecuteCompleted += gpDriveTime_ExecuteCompleted;
   4:                 _gpDriveTime.Failed += (s, a) =>
   5:                 {
   6:                     MessageBox.Show("ServiceArea"+a.Error.Message + "\n" + a.UserState);
   7:                 };
   8:                 _gpPopulation.ExecuteCompleted += gpPopulation_ExecuteCompleted;
   9:                 _gpPopulation.Failed += (s, a) =>
  10:                 {
  11:                     ChangeMapOpacity(-1);
  12:                     MessageBox.Show("Population"+a.Error.Message + "\n" + a.UserState);
  13:                 };

  GP Task是ArcGIS API for Windows Phone提供给我们用来调用Geoprocessing Service的类,与Query Task,Identify Task等相同,使用分为三个步骤:准备参数,提交请求,处理结果。我们首先来看一下CreateDriveTimePolygons这个GP服务所需的参数:

image

  总共有三个参数。Direction为Input的参数,即输入参数,有两个:Input_Location和Drive_Times。因此我们只需准备好这两个参数,即可调用该服务。最后结果中,会包含Direction为Output的参数,即就是我们的结果了(esriGeometryPolygon)。



   1: void _FLayer_MouseLeftButtonDown(object sender, GraphicMouseButtonEventArgs e)
   2: {
   3:     //……
   4:     //prepare parameters for GP Service
   5:     GraphicCollection gc = new GraphicCollection();
   6:     gc.Add(e.Graphic);
   7:     CalculateServiceArea(gc);
   8:     //……
   9: }
  10: mary>
  11: /// find the service area of a given supermarket
  12: /// </summary>
  13: /// <param name="gc">the graphic collection contains the supermarket</param>
  14: private void CalculateServiceArea(GraphicCollection gc)
  15: {
  16:     ESRI.ArcGIS.Client.Projection.WebMercator wm = new ESRI.ArcGIS.Client.Projection.WebMercator();
  17:     GraphicCollection graphicCollection = new GraphicCollection();//using new gc to remain point(121000) stay on map
  18:     foreach (Graphic g in gc)
  19:     {
  20:         Graphic graphic = new Graphic()
  21:         {
  22:             Geometry=wm.ToGeographic(g.Geometry)
  23:         };
  24:         graphicCollection.Add(graphic);
  25:     };
  26:  
  27:     FeatureSet fs = new FeatureSet(graphicCollection);
  28:     List<GPParameter> parameters = new List<GPParameter>();
  29:     parameters.Add(new GPFeatureRecordSetLayer("Input_Facilities", fs));
  30:     parameters.Add(new GPString("Drive_Time_Values", "20 40"));
  31:     //execute the GP Task
  32:     _gpDriveTime.ExecuteAsync(parameters);
  33: }
  34:  
  35: riveTime_ExecuteCompleted(object sender, GPExecuteCompleteEventArgs e)
  36: {
  37:     ESRI.ArcGIS.Client.Projection.WebMercator wm = new ESRI.ArcGIS.Client.Projection.WebMercator();
  38:     foreach (GPParameter parameter in e.Results.OutParameters)
  39:     {
  40:         if (parameter is GPFeatureRecordSetLayer)
  41:         {
  42:             GPFeatureRecordSetLayer gpLayer = parameter as GPFeatureRecordSetLayer;
  43:             //40 min
  44:             gpLayer.FeatureSet.Features[0].Symbol = new SimpleFillSymbol()
  45:             {
  46:                 Fill=new SolidColorBrush(Color.FromArgb(119,153,255,153)),
  47:                 BorderBrush=new SolidColorBrush(Color.FromArgb(255,153,255,153)),
  48:                 BorderThickness=2
  49:             };
  50:             gpLayer.FeatureSet.Features[0].Geometry = wm.FromGeographic(gpLayer.FeatureSet.Features[0].Geometry);
  51:             _GLayer.Graphics.Add(gpLayer.FeatureSet.Features[0]);
  52:             //20 min
  53:             gpLayer.FeatureSet.Features[1].Symbol = new SimpleFillSymbol()
  54:             {
  55:                 Fill = new SolidColorBrush(Color.FromArgb(119, 153, 153, 255)),
  56:                 BorderBrush = new SolidColorBrush(Color.FromArgb(255, 153, 153, 255)),
  57:                 BorderThickness = 2
  58:             };
  59:             gpLayer.FeatureSet.Features[1].Geometry = wm.FromGeographic(gpLayer.FeatureSet.Features[1].Geometry);
  60:             _GLayer.Graphics.Add(gpLayer.FeatureSet.Features[1]);
  61:  
  62:             map1.ZoomTo(gpLayer.FeatureSet.Features[0].Geometry);
  63:             
  64:             //……
  65:             (_BusyIndicator.Child as TextBlock).Text = "查询服务区内人口数......";
  66:             //继续查询该范围人口数
  67:             //……
  68:         }
  69:     }
  70: }
image

  可以看到,我们在parameters集合中添加了GP服务所要求的所有输入参数,然后执行;而结果中,我们也取得了GP服务中的输出参数。这里说明一点,GP服务的参数GPParameter比较灵活,新建参数时的名称,类型一定要保持与Service Directory服务列表中一致,否则可能导致调用失败。另外由于GP服务的空间参考(4326)与我们的底图(102100)不一致,所以做了坐标转换。
  此外我们还注意到一点,CreateDriveTimePolygons服务的执行方式是“esriExecutionTypeSynchronous”即同步调用。这种方式一般用于执行过程时间较短的GP服务,缺点是客户端需要在返回结果之前等待。具体可参考这里。
  获得了超市的服务范围之后,需要用第二个GP服务,PopulationSummary来获得该范围内的人口总数;之后就可按照前面的分析思路,来计算出该超市的营业额预期,与实际营业状况比较后,可得出相应的结论。

image

参考资料:
Geoprocessing概念:http://help.arcgis.com/en/arcgisdesktop/10.0/help/002s/002s00000001000000.htm

ArcGIS Server中的Geoprocessing Service:http://help.arcgis.com/en/arcgisserver/10.0/help/arcgis_server_dotnet_help/index.html#//009300000028000000.htm

ArcGIS API for Windows Phone中调用Geoprocessing Service:http://help.arcgis.com/en/arcgismobile/10.0/apis/windowsphone/help/011v/011v0000001m000000.htm

  至此,《ArcGIS API for Windows Phone开发实例》已经结束,希望大家能通过这几篇文章对ArcGIS API for Windows Phone有所了解。目前API的版本是2.2 beta,这仅仅是一个开始,随着以后大家对移动GIS的关注越来越多,相信esri也会在API中逐步推出更多的功能来满足我们更多的应用需求。
  在前不久召开的MIX11大会上,微软展示了新的Windows Phone Mongo,该版本的SDK中不仅WP的性能有了很大提升,而且有着全面支持真正的多任务,Silverlight 4,内嵌全功能IE9浏览器,支持socket通信,允许同时使用Silverlight和XNA编程模型(SL中就会有原生3D功能支持)等令人兴奋的新特性。伴随着市场占有率不断提升的这一事实,有着真正硬实力的Windows Phone有理由让我们相信不仅对于开发者,同样对于用户而言,它能给我们带来更多机会。

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/