ArcGIS客户端API中加载大量数据的几种解决办法(以Silverlight API为例)

    REST风格的一切事物方兴未艾,ArcGIS Server的客户端API(Javascript/Flex/Silverlight API)也逐渐站上了GIS舞台的中央。虽然客户端API给我们带来了更快捷的开发体验,更丰富的展现效果,但有些(奇怪的)需求还不能直接解决。比如要求在客户端API程序中显示大量图形(上万个),乍看之下,受到平台本身的性能制约无法完成,但我们的思维和时间一样,只要挤一挤,总还是有的。本文就来讨论一下如何在客户端API程序中显示大数据量图形的问题。以Silverlight API为例。

    要将几何对象显示在客户端程序中,一般我们首先想到的办法就是GraphicsLayer。将服务器端的要素通过查询或者其他方式创建成客户端的Graphic用以显示,从而进一步交互。于是有了下面几种方法。

1、Cluster

    比较成熟和理智的做法是,利用Cluster效果来处理大量的Graphic。对于这种办法,ArcGIS Server中的Javascript APIFlex APISilverlight API均能做到。下图是Silverlight API中300,000个点的展示效果。(忽略数据从服务器到客户端的传输时间)

image

    Google MapsBing Maps等也都采取了这种做法。

    但是,不论什么原因,就是不想采用Cluster的办法,而要看到大量密密麻麻的点时心里才觉得踏实该怎么办?对于这种需求,也有对应的解决方案。

2、直接在前台绘制Graphic

    但这种办法直接受到平台性能的限制。以Flex和Silverlight为例,在不影响地图操作的情况下,视图范围内一次性能够承受的Graphic数量大约是4000-5000个。注意,相对于绘制Graphic对象的操作来说,Geometry节点的个数可以忽略,也就是说这几千个Graphic既可以是点,也可以是面。这时候你的地图看起来是这样的(4000个Graphic):

image

3、用ElementLayer(Silverlight API独有)来模拟GraphicsLayer

     ElementLayer是Silverlight API中独有的一种图层类型,直接继承自ESRI.ArcGIS.Client.Layer,用来承载Silverlight本身的一些布局控件(Framework Element)。它的最主要的用途是使得Silverlight本身的控件能够随着地图的放大缩小而自动变化。利用它我们可以在一定程度上模拟GraphicsLayer。比如Silverlight API中的InfoWindow

    GraphicsLayer之所以只能承载一定数目的Graphic,就是因为Graphic对象本身封装的内容比较多。如果我们仅需要展示这些点,并只在上面做一些简单查询,那么可以完全用ElementLayer来替代之。直接的好处就是,可以在客户端显示更多的数据。比如一个点,我们可以用更基本的Ellipse来代替Graphic。将Graphic的Attributes存储在Ellipse.Tag中,依然可以用上面提到的InfoWindow做DataBinding。初步做了一下试验,如果用ElementLayer来模拟GraphicsLayer,在不影响地图操作的情况下,视图范围内一次性能承载的图形个数大约是20,000-25,000个。这时候你的地图看起来像是这样(20,000个Ellipse):

image

    顺便提一下,如果想在ElementLayer中来模拟动态的地图符号,甚至是时态图层也是可以的。对于Graphic,你可以定义Symbol的ControlTemplate;对于ElementLayer,则可以直接将带有任何动画的对象直接加入其中。

    上面提到的3种方法都是纯客户端的,尤其第三种办法,再没有更多工作量的情况下已经基本将客户端的性能用到了极致。但对于想看到密密麻麻点的人来说,五位数的数量级很有可能并不能满足要求。要想将展示的图形个数再提高一个或两个数量级,达到几十万甚至上百万,那么只有来借用服务器端的性能了。

4、ArcGISDynamicMapServiceLayer

    直接将所要展示的数据在服务器端发布成一个MapService,在客户端通过ArcGISDynamicMapServiceLayer来加载。这样的话客户端需要展示的仅仅是一张图片,没有任何压力。功能上,如果想查询的话可以使用Identify/Find/Query Task来达到目的。下图加载了一个本机的ArcGISDynamicMapServiceLayer(AGS10,msd发布,1个实例,core i7 2.6GHz,4g内存,win7 x64),服务只有一个点图层,记录数大约650,000条:

image

    服务器端生成这张图片的时间大约13s,进行一次IdentifyTask操作时间是0.2s。这里大部分时间都花在了服务器端对数据的绘制上,因此局域网传输图片等因素可以暂不考虑。服务器端的绘制速度受硬件和软件两个方面的影响。以ArcGIS Server 10已经无法改变,理论上只能通过提高硬件配置来达到加快出图速度的目的。

    ps:采用ArcGISDynamicMapServiceLayer时可结合服务器端的点抽希办法来一起使用(抽稀方法1抽稀方法2)。但这样就失去了密密麻麻数据带给我们的“成就感”,不在赘述。

    这种办法中,客户端的压力已经完全没有了,功能上也能通过变通来实现,但展现上有两个缺点:1、数据过多的话绘制速度太慢(40w条数据大约是6.7s,12w条数据大约是2.3s,3w条数据大约是0.6s,);2、出图过程中该服务一片空白,且移动过程中也会出现“白边”的现象。如果硬件配置也无法提高,还有没有更好的办法呢?办法是有的。

5、继承TiledMapServiceLayer来达到“动态切图”的目的

    在Google Fusion Tables的例子中,我们可以看到,所有的点数据都是以切片的方式展现,这样的好处是,即使出图速度较慢,用户也能看到切片一个一个的加载过程,不至于长时间一片空白;移动过程中也不会有白边现象;且浏览过的地方都会被客户端缓存,不需要再次向服务器请求,既减少了服务器压力,也加快了显示速度。但明显Google不可能为上传的每张表格都去做切片处理,ArcGIS Server中如何能达到这种效果?

    首先想到的是服务切图时的“Cache On Demand”设置。但第一个用户等待的时间就是服务器完成该比例尺下切图的时间,显然太长;并且切图后如果数据有变化,那么看到的切图就和实际数据不一致了。这条路走不通。

    通过继承TiledMapServiceLayer,可以利用GetUrl方法来控制如何获取每一个切片。此时如果我们以动态地图服务为资源,利用REST SDK的ExportMap操作来生成每一个切片,就达到了“动态切图”的目的。不仅如此,我们还可以在服务器端发布多个相同的服务,在GetUrl方法中轮询使用这些服务,便可以达到“并行”出图的目的。此时每个服务生成的图片都是256*256大小,所包含的记录数会少很多,速度会明显加快。本机发布3个服务时,同样比例尺下的650,000条记录,所有“切片”加载完成大约需要6s,考虑到缓存服务的加载效果,用户完全可以接受。这种方法相比ArcGISDynamicMapServiceLayer,大大提高了出图的速度和效果。

    关于此方法的几点说明:如何利用比例尺、行/列号来计算一个切片的范围,可以参考《ArcGIS Server的切图原理深入》;默认情况下,浏览过的“切片”会缓存在客户端,再次浏览时服务器端就不用动态出图了。如果不想在客户端缓存“切片”,保证服务器端数据发生变化后,客户端也能随时更新,则可以在请求后面加上时间戳,达到DisableClientCache的功能;对于这种数量级的数据展示来说,一般是局域网应用,因此Identify/Find/Query与服务器端的交互完全可以接受;而且通常拥有这种数据量的用户硬件资源一般都是可充分扩展的,因此多发布几个服务来加速出图完全可行。

    无图无真相,看看这种办法的效果。别忘了,在服务器端你发布的仅仅是普通的动态地图服务。

    好了,这几种方法各有千秋,供各位朋友按需选择。

19 thoughts on “ArcGIS客户端API中加载大量数据的几种解决办法(以Silverlight API为例)”

  1. Pingback: Portable Basemap Server:多数据源多客户端的底图服务器 - 菩提老王的葡萄架

  2. tao_olive

    兄弟,请问下在服务器端,怎么能够根据每个用户输入的条件,动态生成图层?
    因为我们现在每个用户都需要能够控制graphic的颜色,而且数据量4K以上,
    刚才看了兄弟提的ArcGISDynamicMapServiceLayer,这个能够根据每个用户的不同,动态在服务器生成不同的图层吗?

    1. diligentpig

      首先还是推荐采用featurelayer+聚类的方式,在客户端来实现。featurelayer可以设置where条件,从而实现根据不同用户显示不同graphic的需求;如果非要一次显示4k点,用上面的dynamictiledmapservicelayer也可以实现,也可根据不同用户设置不同的layerdefinition条件,服务器端来渲染即可。

      1. tao_olive

        ArcGISDynamicMapServiceLayer使用的话,必须得在客户端画graphic才能满足不同用户,使用不同的颜色进行渲染,不知有没有在服务器端根据用户的选择的颜色,进行配置,再用请求的服务的方式,进行展示,这样数量就没有限制了,因为显示的是图片了

        1. diligentpig

          如果用户所要求的渲染方式可预期,种类相对较少,可以考虑提前在服务端发布多种渲染的动态服务,供客户端直接调用;如果用户要求渲染方式灵活,需要自定义,目前只能在客户端graphicslayer做,如果数据量大,就要采用cluster/scale dependence/maxoffset之类的技术来减少客户端压力;如果两者要兼得,可以期待10.1带来的新功能,在服务器端动态渲染。

  3. Leon

    老王,我现在采用你说的用elementlayer加载大量数据,但碰上问题了,我要做的是gps监控,gps数量可能达到2万,我每个gps都会实例成一个usercontrol,每个usercontrol本身都会包含一个infowindow(用来显示自身信息),一个图片(用来显示自身样式),3个Ellipse(分别显示不同预警时的动画),这种情况下,加载到300个左右时,界面操作明显卡,怎么办呢?问题出在哪里?帮忙分析下吧

    1. diligentpig

      从你的描述来看,usercontrol过于复杂。想象一下,同时2w个图形,每个图形三个点,还有各自的动画,这个页面估计会惨不忍睹吧?如果要尽可能的多显示点,建议:1、使用elementlayer;2、使用简单图形如ellipse,而不是usercontrol来模拟graphic;3、可将属性信息保存到ellipse的tag内或去服务器端查询,再用一个usercontrol模板来模拟infowindow,而不是将其嵌入到每个图形中去。

      1. leon

        我改用ellipse了,为了测试数量,我没有其他数据,只是一个ellipse,这样做也是在达到4000到5000时开始有明显卡顿现象,我用聚合分类解决问题了,可是,真的只能聚合分类吗?想看实实在在的点不可以吗?

  4. yunshan

    王哥,我用ElementLayer加载了3000多个柱状图(柱状图用visifire做的),速度太慢有好的解决方案吗?

    1. 菩提老王 Post Author

      设置比例尺的可见性吧,这些柱状图不应该同时出现在一个屏幕上的。点的话数量多一些,视觉上还能看清楚,柱状图的话真是看不清了,同时显示出来也没用了。

      1. yunshan

        我只是在大比例尺下一次加载这些柱状图,小比例尺下不加载,由于图层的原因,只能一次加载这些柱状图,但是加载的速度太慢

        1. 菩提老王 Post Author

          可以尝试在单独线程中添加;另外如果有了比例尺依赖这个前提,也可类似featurelayer的ondemand模式,只加载当前屏幕的内容即可。

          1. yunshan

            王哥,我采用这个方法速度还是太慢,我的柱状图是动态生成的,是不是这个影响了加载速度

          2. 菩提老王 Post Author

            如果总共有3000图形,你采用比例尺依赖+分屏按需加载,设置合理的话一次加载(一屏幕范围)不会超过几十个吧,这样还慢你就需要检查代码了,看看是慢在哪然后改进。

  5. luxx

    前辈,为了显示大量数据,我们采用ArcGISDynamicMapServiceLayer来显示要素点,针对某个图层,可以用query等进行查询,但是要对某个图层上的要素点进行编辑、添加、删除点操作就不好弄了,我们想封装rest API来做,除此之外,arcgis runtime for wpf 还有没有其他的方式来做?谢谢!

    1. 菩提老王 Post Author

      不用封装api。可以用同一份数据,发布两个服务,一个map service用于显示,一个feature service(发布map service时勾选feature access)用于编辑。由于两者数据源一直,所以通过feature service编辑的结果能够实时反映到map service中去。
      此外,如果你有最新版的arcgis runtime sdk for wpf,它的高性能可保证同一屏幕内显示1w个点以上的graphic也可流畅操作,所以你只需发布一个feature service就够了,用于编辑和显示。

      1. luxx

        前辈,我们发布两个服务,一个map service用于显示,一个feature service用于编辑。但是怎么使用feature service用于编辑呢?用FeatureLayer加载不了那么多数据,就没法绑定鼠标事件,用MapService可以显示数据,可以用identify查找,但是拿到数据编辑后怎么保存呢?还望指教。

        1. 菩提老王 Post Author

          Feature Service的主要目的就是用于在线编辑的,js/flex/sl api中都有具体例子与详细的概念描述,请参考具体的帮助文档。
          FeatureLayer加载feature service数据时,可以用ondemand模式(请查阅api帮助)或利用比例尺依赖的方式(在一屏幕能显示足够让性能减慢的点时,不显示该图层,在比例尺放大到一定程度时在显示)来解决数据量问题。

Leave a Reply to diligentpig Cancel Reply

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

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>