2011年3月29日星期二

ArcGIS移动客户端离线底图的几种解决方案

  移动GIS中,通常将数据分为两大类:basemap layer和operational layer。前者是指漫游或导航时起参考作用的图层,这些图层内容通常不会变化,只起到视觉辅助作用,称为底图图层;后者是指存储GIS数据的图层,比如可通过这些图层来提供属性/空间查询操作,或者对其内容进行编辑,然后与服务器端进行同步,称为业务图层。
  目前ArcGIS移动产品有5种,基于Windows Mobile平台的ArcPad和ArcGIS Mobile,这两个产品已经很成熟了,都有各自的离线缓存格式,其中ArcGIS Mobile从10版本开始,可以直接读取ArcGIS Server缓存地图服务的切片文件做为basemap layer,支持exploded和compact两种格式。
  相对于以上两个老牌移动产品,三个刚出道的小弟ArcGIS for iOS,ArcGIS for Android和ArcGIS for Windows Phone就走了不同路线:依赖于ArcGIS Server的REST服务。因此几乎所有操作,包括显示地图,都需要用到ArcGIS Server发布的各种服务。这三个产品的离线功能将来肯定是会有的,但具体的时间表还无法确定。
  针对ArcGIS for iOS/Android/Windows Phone,本文提出3种可行的离线底图(basemap layer)的解决方案,供各位参考。以ArcGIS for Windows Phone为例。

1、ArcGIS Server地图服务的Exploded格式缓存文件

  ArcGIS API for Windows Phone中,提供了ArcGISTiledMapServiceLayer用来加载ArcGIS Server发布的缓存地图服务,它的原理是Map控件计算好需要加载的切片的row,col,level参数,利用ArcGISTiledMapServiceLayer里的GetTileUrl方法提供如何获得指定参数的切片文件,最后拼接成完整的地图。
  因此我们可以通过继承ArcGISTiledMapServiceLayer的父类,TiledMapServiceLayerTiledLayer,来实现自己的自定义图层,比如用它来加载Google Maps天地图等各种地图。加载这些在线地图都是通过重写GetTileUrl()方法来实现的。
  对于已经存放在硬盘上的缓存文件,该如何加载呢?这几个图层还有一个方法,GetTileSource。这个方法有一个onComplete action,可以传入ImageSource类型的参数,它比GetTileUrl来的更直接。其实GetTileSource方法中调用了GetTileUrl方法的结果(一个获得tile的url字符串),利用这个字符串向服务器端发送请求,请求回来的结果就是切片图片的二进制流,再将这个二进制流形成ImageSource,通过onComplete方法返回。
  所以我们可以抛开GetTileUrl,直接重写GetTileSource方法,来根据row,col,level参数,读取地图服务的缓存文件。首先将Exploded格式的地图服务缓存文件拷贝到手机中:

image

  包含conf.cdi(ArcGIS Server 10版本中才有,记录了缓存的全图范围)和conf.xml文件的好处是,我们可以在代码中读取这两个文件来动态生成我们的Tiling Scheme,以完成图层初始化的工作。从配置文件中读取参数后,就可以重写GetTileSource方法了。部分代码如下:

   1: protected override void GetTileSource(int level, int row, int col, Action<System.Windows.Media.ImageSource> onComplete)
   2:         {
   3:             string f = string.Empty;
   4:             if (_cacheTileFormat.ToLower().Contains("png"))
   5:                 f = ".png";
   6:             else if (_cacheTileFormat.ToLower().Contains("jpeg") || _cacheTileFormat.ToLower().Contains("jpg"))
   7:                 f = ".jpg";
   8:             else
   9:                 throw new Exception("切片格式不明:" + _cacheTileFormat);
  10:             #region Exploded读取
  11:             if (_storageFormat == StorageFormat.esriMapCacheStorageModeExploded)
  12:             {
  13:                 string baseUrl = _path;// "/WP_LocalCacheReader;component/Assets/usa_exploded/"
  14:                 baseUrl += @"/_alllayers";
  15:                 string l = "L";
  16:                 l = level.ToString().PadLeft(2, '0');
  17:                 string r = "R";
  18:                 r = String.Format("{0:X}", row).PadLeft(8, '0');
  19:                 string c = "C";
  20:                 c = String.Format("{0:X}", col).PadLeft(8, '0');
  21:                 string str = baseUrl
  22:                     + @"/L" + l
  23:                     + @"/R" + r
  24:                     + @"/C" + c + f;
  25:                 BitmapImage img = new BitmapImage(new Uri(str,UriKind.RelativeOrAbsolute))
  26:                 {
  27:                     CreateOptions = BitmapCreateOptions.DelayCreation
  28:                 };
  29:                 img.ImageFailed += (s, a) =>
  30:                 {
  31:                     string uri = _path + "/missing" + _tileRows.ToString() + f;
  32:                     BitmapImage image = new BitmapImage(new Uri(uri, UriKind.RelativeOrAbsolute))
  33:                     {
  34:                         CreateOptions = BitmapCreateOptions.DelayCreation
  35:                     };
  36:                     onComplete(image);
  37:                     return;
  38:                 };
  39:                 onComplete(img);
  40:             }
  41:             #endregion
  42:         }


  当指定的切片文件不存在(也许还未创建)时,可以加载事先准备好的missing图片来替换。


2、ArcGIS Server地图服务的Compact格式缓存文件


  这是ArcGIS Server 10推出的新的缓存格式,缓存图片都保存在.bundle文件中,一个bundle目前可存储128*128张切片。切片文件更少,主要目的是为了迁移方便。文档中并未给出读取这种格式文件的方法,不过牛魔王已经凭空推断出了这种格式的内容,这里就借鉴了他的方法。还是先将缓存文件拷贝到手机中:
image


  利用conf.cdi和conf.xml获得tiling scheme,之后重写GetTileSource方法。具体思路牛魔王文中已经给出,感兴趣的同学还是看原文,学习牛牛的思路比较好。
  下面是读取两种缓存文件的效果:
image


image


image


3、第三方离线地图文件


  除了ArcGIS Server的缓存切片之外,我们还可以读取第三方的离线地图文件来做为我们的底图。比如以前面介绍过的Mobile Atlas Creator为例,我现在已经有了很多自己下载好的离线地图,如果能在ArcGIS移动客户端使用起步两全其美?其实在目前的离线导航软件中,很多都用sqlite数据库做为地图存储格式,因为它应用广泛,轻巧,紧凑,Android,iOS,Symbian等系统对它都有原生的支持。Mobile Atlas Creator中,RMaps和OruxMaps都用Sqlite保存离线地图。这里以应用较为广泛的RMaps格式为例,进行试验。
创建好的RMaps地图文件如下:
image


  我们利用FireFox里的Sqlite Manager插件先来查看一下数据库的内容:
image


  可以看出,我们所需的内容都保存在tiles这张表中,而x,y,z三个参数与我们所需的row,col,level很像。经过试验(保存一个全球范围的地图),很快验证出level=17-z。
  参数有了,要如何读取切片呢?对于Sqlite,虽然目前Windows Phone还没有提供原生的支持,不过codeplex上已经有不少项目都提供了解决办法。我选择Sqlite Client for Windows Phone来读取RMaps的地图文件。下面是RMaps离线地图(Bing Maps)和ArcGIS Online上StreetMap叠加的效果:
image
  需要说明的是,不论是RMaps还是OruxMaps,都没有在数据库中保存tiling scheme的相关参数,所以我们不能为图层提供诸如FullExtent之类的参数。但这丝毫不影响我们的使用,我们可以为Map控件显示指定Extent,这样就可以直接显示我们的离线地图了。除了自己的地图数据之外,基本上所有数据源都使用一种空间参考,102100或者3857,你懂的。
  这样即使我们没有ArcGIS Server软件,也能制作自己的底图了。这里有网友们已经下载好的各个城市的RMaps格式文件
  相对于RMaps之类的离线地图软件,ArcGIS的移动产品的优势除了不仅能够任意叠加地图数据,还有GraphicsLayer和服务器端强大的功能支持,在配合Windows Phone本身的SDK功能,你也可以做出一个功能全面的导航软件来。


  关于离线地图文件的打包。1、在Silverlight程序中,Build Action的选择决定了文件最后的保存位置,比如你选择Resource,则会嵌入到工程的dll中,如果选择Content,则会保存在dll之外,xap文件之内。以上三种解决方案里,我们可以选择任意的Build Action,这样地图都会通过xap部署到手机里;如果有需要,我们还可以将文件拷贝到程序的IsolatedStorage中去。2、对于Exploded格式的缓存,WP的编程建议中提到,媒体文件Build Action设为Content效率会更高。3、对于Exploded格式的缓存,如果为了拷贝方便,我们也可以将其打包为.zip文件,部署到手机中,在程序加载的时候再将其解压缩来读取。
  本文以ArcGIS for Windows Phone为例,讨论了3种离线底图的解决方案,文中所涉及的所有功能,ArcGIS for iOS和ArcGIS for Android同样适用。

没有评论:

发表评论