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

17 thoughts on “ArcGIS移动客户端中可以自动离线的底图图层”

  1. yozo

    老王,你源码的重写Initialize初始化时,当是离线时,不能调用[ArcGISTiledMapServiceLayer]base.Initialize();这样还是会去获取Schema,会报错的,应该在条件里分别处理。

      1. GISer

        没有网络连接时,调用基类的initialize方法时会报The remote server returned an error: NotFound.这个怎么处理,想了也查了很多方法也没查到怎么处理,请王哥帮帮忙

  2. Roscoe S. Bates

    我们都知道,在使用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空间内),无需人工干预;这样在没有网络连接的情况下,就自动加载先前保存过的切片,作为离线图层使用(程序代码无需做任何修改)。

  3. ssd

    😥
    我用的arcgis.cleint 是3.0的
    ArcGISTiledMapServiceLayer这个类中的getTitleSource是不可以被重写的。
    怎么办?

    1. diligentpig

      3.0开始ArcGISTiledMapServiceLayer在gettilesource这个方法前加了sealed关键字,所以没法继承此类去重写这个方法了。但你可以集成TiledMapServiceLayer或TiledLayer去重写这个方法,这也是完成此功能所必须的(需要多做一些工作),请注意看上面的评论内容。

  4. qiji

    楼上的朋友 不知道你是否实现了离线功能;我现在也遇到这个问题了,能否告知你如何实现的?

  5. hodldly

    读取cdi和xml时,重写[TileLayer]base.Initialize();时报Index was outside the bounds of the array.这个异常,请问这个怎摸处理啊谢谢

  6. quxiangan

    老王你好,我用的是10.1.1的api,offlineablelayer继承自tiledlayer,但是不知道还要做怎样的工作才可以实现离线底图。如果用arcgisdynamiclayer的话,思路一样,但是要怎么做呢

    1. 菩提老王 Post Author

      继承自tiledmapservicelayer的图层需要实现initialize(设置空间参考)和gettileurl方法,具体可参考:http://help.arcgis.com/en/webapi/silverlight/apiref/ESRI.ArcGIS.Client~ESRI.ArcGIS.Client.TiledMapServiceLayer.html
      继承自tiledlayer的话,没有直接的gettileurl方法,但可以复写更直接的gettilesource方法。

      1. GISer

        谢谢老王,如果是tiledlayer,是不是还得加个依赖属性Url,然后Url怎么个用法呢,gettiledsource是如何地图被调用的,我有点乱,能麻烦您给我讲解下么

        1. 菩提老王 Post Author

          不用加,tiledmapservicelayer最终也会调用gettilesource方法,所以写了后者就不用写前者了。这是编程上有关类的继承的知识,你可以自己查查相关资料,结合reflector这个工具看看两个类的方法就会明白了。

Leave a Comment

Your email address will not be published. Required fields are marked *