ArcGIS RIA开发实践 3

I、 ArcGIS Flex API 高级
控制Map与Layer
1 Map 中地图服务图层的控制

Map中图层的控制是很多人首先关心的问题,因为根据业务来切换图层的状态是经常遇到的一种需求。

首先让我们看一下对图层可见性的控制,Layer本身就有一个visible属性,这个属性指示的就是当前图层是否在Map中显示。不管是MapServiceLayer还是GraphicLayer,如果设置了visible为false,那么在这个图层是不会被显示的。

除了控制图层是否显示,我们还可以控制图层的透明度,这是由Layer的alpha属性来控制的。这样我们可以将多个图层叠加复合显示,这时候会给用户一种复合的多信息量的地图:

图 22 多图层透明复合效果

另外,图层的叠放顺序也是一个经常遇到的问题,在ArcGIS Flex API中,后加入的地图被放置在最上层,同时,最底层的索引(index)为0。

图 23 Map中Layer的存放顺序

Map中有一个layers属性存放了地图中所有Layer的集合,另外还有layerIds属性存放了对应的所有Layer的id的集合,每当添加一个Layer到Map中的时候,ArcGIS Flex API会给这个图层一个id并存放到layerIds中保存。通过调用Map的reorderLayer方法可以给这些图层重新排序。

下面我们在Map中添加3个图层,并在点击地图的时候把最上层的图层移到底层,从而实现图层叠置顺序的改变:

<!-- [CDATA[ <p><b>private</b> <b>function</b> init():<b>void</b> <p>{ <p><b>var</b> layer:Layer; <p>layer = <b>new</b> ArcGISTiledMapServiceLayer(<b>"http://server.arcgisonline.com</b> <p><b>/ArcGIS/rest/services/ESRI_Imagery_World_2D/MapServer"</b>); <p>map.addLayer(layer); <p>layer = <b>new</b> ArcGISTiledMapServiceLayer(<b>"http://server.arcgisonline.com</b> <p><b>/ArcGIS/rest/services/NPS_Physical_World_2D/MapServer"</b>); <p>map.addLayer(layer); <p>layer = <b>new</b> ArcGISTiledMapServiceLayer(<b>"http://server.arcgisonline.com</b> <p><b>/ArcGIS/rest/services/ESRI_StreetMap_World_2D/MapServer"</b>); <p>map.addLayer(layer); <p>} <p><b>private</b> <b>function</b> onMapClick():<b>void</b> <p>{ <p>map.reorderLayer(map.layerIds[map.layers.length-1], 0); <p>} <p>]]> <p></mx:Script> <p><esri:Map id="map" click="onMapClick()"/> <h6><a>2 地图服务图层中子图层的控制</a></h6> <p>不仅Map中所有Layer是可控制的,一个Layer中的内容也是可控制的。当然,GraphicLayer本身就是客户端的图层,对它的控制自然不在话下;这里我们主要讲的是对MapServiceLayer的控制,更确切地说是对ArcGISDynamicMapServiceLayer的控制&mdash;&mdash;毕竟TiledMapService已经生成了图片,再进行内容上的控制是不可能的。 <p>大家都知道知己知彼百战不殆是至理名言,要想对动态地图服务中的内容进行控制,当然首先需要知道服务中有些什么内容。比如我想控制某个服务只显示其中的某些图层,那么首先必须知道总共有哪些图层才可以。 <p>ArcGISDynamicMapServiceLayer有一个layerInfos属性,这个属性是一个数组,它存放了这个地图服务中所有子图层的相关信息。比如我这里有一个世界地图的服务: <p>图 24 世界地图服务 <p>在浏览器中打开这个服务的REST地址可以看到以下的信息: <p>图 25 通过REST接口查看地图服务的子图层 <p>在ArcGIS Flex API项目中调试你可以看到layerInfos就是一个长度为4的数组,数组中存放了跟上面看到的列表类似的信息: <p>图 26 layerInfos的值 <p>值得注意的是,这里子图层的id虽然也是从0开始,但是和Map中的地图服务图层索引正好相反,最上层的子图层为0。 <p>另外,这里还不得不用一些篇幅来特别说明一下关于地图服务加载的话题。当我们把一个ArcGISDynamicMapServiceLayer或ArcGISTiledMapServiceLayer添加到Map中的时候,初始状态只不过是在Map中添加了一个自定义的UIComponent;然后,这些地图服务图层会根据url属性的设置向服务器发送请求,获取关于这个地图服务的描述,这个过程称之为&ldquo;加载&rdquo;;当加载完成以后,这个Layer在Map中就算是定位了,然后它会根据Map的操作来计算自身需要从ArcGIS Server服务器获取什么数据;随即获取数据然后再显示出来。以上最关键的过程是&ldquo;加载&rdquo;的完成,只有在加载完成以后,Layer的load监听被触发,这个时候地图服务图层的layerInfos属性才会有值,这个图层才算真正可以使用。 <p>当我们获得layerInfos以后,我们就可以控制服务中的子图层了。控制通过ArcGISDynamicMapServiceLayer的visibleLayers属性实现,visibleLayers对应一个数组,这个数组存放了需要显示的所有子图层的id。比如上面的世界地图服务,如果我设置visibleLayers值为[0, 2, 3],那么id为1的国界图层就会被隐藏掉,地图会变成如下的效果: <p>图 27 设置visibleLayers属性后的世界地图服务 <h6><a>3 子图层中内容的控制</a></h6> <p>上面我们了解了Map中地图服务图层的控制和地图服务图层中子图层的控制,这个小节里我们再来看一下如何控制某个子图层中的内容。 <p>ArcGISDynamicMapServiceLayer有一个layerDefinitions属性,这个属性对应的是一个和layerInfos长度相同的数组&mdash;&mdash;你也可以联想到,这个数组中的对象和子图层是一一对应的。 <p>layerDefinitions数组中的对象是字符串,索引从0开始,每个字符串对应一个子图层。这个字符串是SQL语句中条件子句的WHERE部分,它可以使满足条件的要素才被显示。 <p>下面的MXML给上面使用过的世界地图服务图层设置了一个layerDefinitions值,它给id为1(可以看到它是layerDefinitions数组中的第2个元素)的国界子图层做了一个过滤,让它只显示中国的国界: <p><esri:ArcGISDynamicMapServiceLayer <p>url="http://localhost:8399/arcgis/rest/services/AgsSample/World/MapServer"> <p><esri:layerDefinitions> <p><mx:Array> <p><mx:String></mx:String> <p><mx:String><![CDATA[CNTRY_NAME='China']]></mx:String> <p><mx:String></mx:String> <p><mx:String></mx:String> <p></mx:Array> <p></esri:layerDefinitions> <p></esri:ArcGISDynamicMapServiceLayer> <p>其中&ldquo;<![CDATA[&rdquo;和&ldquo;]]>&rdquo;是XML解析器的忽略标签,标签中的&ldquo; CNTRY_NAME='China' &rdquo;就是对图层1设置的过滤条件。 <p>下面是按照上面的代码设置了layerDefinitions属性值后的世界地图服务图层效果。 <p>图 28 设置layerDefinitions属性后的世界地图服务 <p>在这里你可以想象一下,你是不是可以根据你的业务来随意设定在地图上需要表现出来的要素了呢? <h6><a>4 动态投影</a></h6> <p>对于动态地图服务(Dynamic Map Service),你可以根据需要设置不同的空间参考,从而实现动态的投影。 <p>对Map的投影设置可以通过给Map指定不同空间参考的extent属性来实现,下面的代码给Map设置了Well-Known ID为102113的空间参考,该空间参考采用WGS84地理坐标和Web Mercator投影: <p><esri:Map> <p><esri:extent> <p><esri:Extent spatialReference="{<b>new</b> SpatialReference(102113)}" <p>xmin="-20037508.342787" xmax="20037508.342787" <p>ymin="-20037508.342787" ymax="20037508.342787"/> <p></esri:extent> <p><esri:ArcGISDynamicMapServiceLayer <p>url="http://localhost:8399/arcgis/rest/services/AgsSample/World/MapServer"/> <p></esri:Map> <p>这个102113空间参考在ArcGIS Server中已经有定义,事实上ArcGIS Flex API只是保存了这个空间参考的id,本身并不作任何的计算。另外,设置的Well-Known ID必须是ArcGIS Server中已有的定义,具体可用的空间参考列表可以在REST SDK文档中找到。 <p>我们可以看到,通过上面的设置,Map中呈现的地图跟原来4326空间参考下的地图明显不同: <p>图 29 设置投影为Web Mercator的世界地图服务 <h5><a><b>二 </b><b>业务信息的表达</b></a><b></b></h5> <h6><a>1 分类渲染</a></h6> <p>地图服务图层的分类渲染是在MXD文档中定义的,因此没有太多可讨论的,这里主要讨论的是GraphicLayer的分类渲染。当你从服务器获得一些数据想标注到GraphicLayer上,同时希望这些Graphic根据不同的属性值来使用不同的符号标注的时候,这个问题就出现了。 <p>当然,比较笨拙的办法就是每绘制一个Graphic之前先判断一下它的属性,然后根据判断的情况来给Graphic赋一个不同的符号值&mdash;&mdash;这样的写法或许会让你觉得代码有点混乱,因此,我们这里要讲的是另外一种比较好维护,代码更加优雅的方式。 <p>GraphicLayer有个属性是symbolFunction,这个属性对应的是一个函数类型的对象,在这里我们可以定义一个函数来对符号进行判断。 <p>下面的代码实现了这样的功能:从美国1999年州人口统计数据中,根据人口的分布区间,使用不同的颜色进行表示。 <p><mx:Script> <p><![CDATA[ <p><b>private</b> <b>function</b> getSymbol(g:Graphic):Symbol <p>{ <p><b>var</b> symbol:SimpleFillSymbol = <b>new</b> SimpleFillSymbol(); <p><b>var</b> pop:Number = g.attributes[<b>"POP1999"</b>]; <p><b>if</b>( pop<1000000 ) <p>{ <p>symbol.color = 0x00FF00; <p>} <p><b>else</b> <b>if</b>( pop>=1000000 && pop < 5000000 ) <p>{ <p>symbol.color = 0x55FF00; <p>} <p><b>else</b> <b>if</b>( pop>=5000000 && pop < 10000000 ) <p>{ <p>symbol.color = 0xAAFF00; <p>} <p><b>else</b> <b>if</b>( pop>=5000000 && pop < 10000000 ) <p>{ <p>symbol.color = 0xFFFF00; <p>} <p><b>else</b> <b>if</b>( pop>=10000000 && pop < 15000000 ) <p>{ <p>symbol.color = 0xFF9900; <p>} <p><b>else</b> <b>if</b>( pop>=15000000 && pop < 20000000 ) <p>{ <p>symbol.color = 0xFF4400; <p>} <p><b>else</b> <p>{ <p>symbol.color = 0xFF0000; <p>} <p><b>return</b> symbol; <p>} <p>]]> <p></mx:Script> <p><esri:Map id="map"> <p><esri:ArcGISTiledMapServiceLayer <p>url="http://server.arcgisonline.com/ArcGIS/rest/services/ESRI_Imagery_World_2D/MapServer"/> <p><esri:GraphicsLayer id="graphicLayer" symbolFunction="getSymbol"/> <p></esri:Map> <p>GraphicLayer的symbolFunction属性对应的函数的参数是Graphic对象,其返回值为Symbol对象,这就把GraphicLayer上的Graphic和它应该对应的Symbol联系了起来。在上面的代码中我们给每个Graphic都新建了一个Symbol对象,实际应用时应该减少Symbol的数量,比如我有n个区间需要分类渲染,那么可以预先定义n个Symbol,然后在SymbolFunction中根据不同的条件选择已经定义好的Symbol。这也是在Flex开发中需要遵循的一个原则:能重用的对象不要重新生成。 <p>下面是实现GraphicLayer分类渲染的效果: <p>图 30 GraphicLayer的分类渲染 <p>在ArcGIS Flex API 1.2版本中新添加了一个功能类Renderer,它可以更方便地实现地图的客户端渲染,主要有这样两种Renderer:ClassBreaksRenderer和UniqueValueRenderer,看这个名字是不是感到很亲切?下面让我们看一下用ClassBreaksRenderer来实现的分类渲染: <p><mx:Script> <p><![CDATA[ <p><b>private</b> <b>function</b> initApp():<b>void</b> <p>{ <p><i>// </i><i>添加100随机的Graphic,注意它的ranking属性</i> <p><b>for</b> (<b>var</b> i:int; i < 100; i++) <p>{ <p><b>var</b> x:Number = Math.random() * 360 - 180; <p><b>var</b> y:Number = Math.random() * 180 - 90; <p><b>var</b> attributes:Object = { <b>"ranking"</b>: Math.random() }; <p><b>var</b> g:Graphic = <b>new</b> Graphic(<b>new</b> MapPoint(x, y), <b>null</b>, attributes); <p>graphicsLayer.add(g); <p>} <p>} <p>]]> <p></mx:Script> <p><esri:SimpleMarkerSymbol id="smallSym" size="6" color="0xFF0000" alpha="0.7"/> <p><esri:SimpleMarkerSymbol id="medSym" size="10" color="0xFF0000" alpha="0.7"/> <p><esri:SimpleMarkerSymbol id="largeSym" size="16" color="0xFF0000" alpha="0.7"/> <p><esri:Map> <p><esri:ArcGISTiledMapServiceLayer url="http://server.arcgisonline.com/ArcGIS/rest/services/ESRI_StreetMap_World_2D/MapServer"/> <p><esri:GraphicsLayer id="graphicsLayer"> <p><esri:renderer> <p><esri:ClassBreaksRenderer attribute="ranking"> <p><esri:ClassBreakInfo maxValue="0.33" symbol="{smallSym}"/> <p><esri:ClassBreakInfo minValue="0.33" maxValue="0.67" symbol="{medSym}"/> <p><esri:ClassBreakInfo minValue="0.67" symbol="{largeSym}"/> <p></esri:ClassBreaksRenderer> <p></esri:renderer> <p></esri:GraphicsLayer> <p></esri:Map> <p>上面的代码在地图上随机添加了100个Graphic,每个Graphic随机指定一个&ldquo;ranking&rdquo;属性值,GraphicLayer的ClassBreaksRenderer根据这个&ldquo;ranking&rdquo;属性值的分布来决定使用大、中、小三种符号中的哪种符号进行渲染: <p>图 31 使用ClassBreaksRenderer实现的分类渲染 <h6><a>2 专题图</a></h6> <p>这一小节让我们去看一下如何使用专题图在地图上表现丰富的业务数据。 <p>专题图主要通过InfoSymbol来实现,它可以显示和InfoWindow类似的符号。在InfoSymbol中放置比如饼图、柱状图等组件后,其对应的Graphic就可以被绘制成一个信息非常丰富的符号,同时,符号内的数据和Graphic的attributes是相关联的。 <p>下面首先看一下使用饼图的专题图效果: <p><a>图 </a>32 InfoSymbol实现的专题图效果 <p>以下是对应的源代码: <p><mx:Script> <p><![CDATA[ <p><b>private</b> <b>function</b> init():<b>void</b> <p>{ <p><b>var</b> mp:MapPoint = <b>new</b> MapPoint( x, y ); <p><b>var</b> g:Graphic = <b>new</b> Graphic(mp); <p>g.attributes = <b>new</b> Object(); <p><b>var</b> thematic:ArrayCollection = <b>new</b> ArrayCollection( [ <p>{ Country: <b>"</b><b>美国"</b>, Gold: goldA }, <p>{ Country: <b>"</b><b>中国"</b>, Gold: goldC }, <p>{ Country: <b>"</b><b>俄罗斯"</b>, Gold: goldR } ]); <p>g.attributes.thematic = thematic; <p><b>this</b>.thematicLayer.add(g); <p>} <p>]]> <p></mx:Script> <p><esri:InfoSymbol id="infoSymbol" infoPlacement="center"> <p><esri:infoRenderer> <p><mx:Component> <p><mx:PieChart id="chart" <p>showDataTips="true" width="150" height="150" <p>dataProvider="{data.thematic}"> <p><mx:series> <p><mx:PieSeries field="Gold" displayName="资源占有率" <p>nameField="Country"/> <p></mx:series> <p></mx:PieChart> <p></mx:Component> <p></esri:infoRenderer> <p></esri:InfoSymbol> <p><esri:Map> <p><esri:ArcGISTiledMapServiceLayer url="http://server.arcgisonline.com/ArcGIS/rest/services/ESRI_Imagery_World_2D/MapServer"/> <p><esri:GraphicsLayer id="thematicLayer" symbol="{infoSymbol}" /> <p></esri:Map> <p>在这个例子里,我们在程序初始化的时候生成了一些带有统计信息的Graphic添加到地图上,这些Graphic对象的attributes属性中都带有一个thematic集合来保存各个统计的对象,每个统计的对象包含两个字段:Country表示国家,Gold表示资源占有率,下面我们会在InfoSymbol的定义中再次看到这两个字段。 <p>当定义好这些Graphic对象以后,我们就可以把它们添加到设置了InfoSymbol符号的GraphicLayer上了。在InfoSymbol的定义中,我们可以看到,在这个InfoSymbol中添加了一个饼图组件PieChart,这个饼图的dataProvider属性绑定的是{data.thematic},它代表的其实就是Graphic对象的attributes属性的thematic对象。你可以简单地这样认为:InfoSymbol中的data代表的就是其对应的Graphic对象的attributes属性。 <p>既然在InfoSymbol中可以获得Graphic的属性信息,那么根据Graphic的属性信息来绘制不同的专题图就是水到渠成的事情了,上面的代码就可以做出图 30的效果来。 <h6><a>3 实时的数据变化</a></h6> <p>Flex应用可以和服务器建立Socket连接,从而真正实现从服务器向客户端&ldquo;推送&rdquo;数据,这使构建真正的实时监控平台等应用成为可能&mdash;&mdash;当然,如果你采用定时从服务器刷新数据这种方式来构建&ldquo;准&rdquo;实时的应用,那么从ArcGIS Flex API的角度来说也没什么区别。在这个小节中我们主要来看一下当刷新数据的时候我们应该怎么改变原先Graphic对象的位置,以及在构建实时的应用时需要注意的一些特殊的细节。 <p>当我们接收到数据,从而需要更新某个对象的位置或属性的时候,首先需要从一个GraphicLayer上获得那个特定的Graphic(如果获取不到则新建)。由于所有Graphic都是GraphicLayer的子对象,因此,我们可以通过GraphicLayer的getChildAt或getChildByName方法来获取一个Graphic。 <p>更改这个Graphic的位置其实就是更新这个Graphic的geometry属性,把这个属性更新以后,这个Graphic会自动重绘到新的位置去。 <p>一般的实时监控应用可能会有比较多的Graphic,因此下面我们就几个比较重要的细节做点说明。 <p>首先是资源嵌入的问题,如果你使用了PictureMarkerSymbol之类的符号,它将会使用一个图片资源进行渲染,一旦你没把这个图片资源嵌入到SWF中去,每次使用这个Symbol的时候应用都会从服务器动态获取这个图片再标注到地图上,因此这里会额外存在一个加载的过程,而且这个图片绘制的效果会比较差。因此,最好把使用到的资源图片等都嵌入到当前应用中来,这样可以美化绘制的效果,同时提升绘制的效率。 <p>还有一个是Graphic的autoMoveToTop属性,这是一个默认为true的属性,这种情况当鼠标移动到这个Graphic对象上的时候,这个Graphic会自动被移动到所有Graphic对象的最顶端,当Graphic数量比较多、并且Graphic相互叠置比较多的时候,这会消耗比较多的资源进行计算。因此在这种情况下,最好把这个选项设为false。 <p>另外,对于浏览器中的Flash Player大概可以承受的Graphic的数量级,最好也心里有数。我之前做过一些相关的测试,如果Graphic之间没有相互叠置,那么大概在5000个静态的(不刷新)Graphic之内用户的操作体验还是可以的&mdash;&mdash;事实上如果Graphic太多了在屏幕上也很难看清楚。如果业务数据量实在非常大,而且想用Graphic来表示,那么最好还是考虑对数据做些处理,比如使用热点图来表示数据分布等方法。 <p>下面是我实现的一个实时监控Demo的效果,这个Demo中监控了1000个不同的对象,服务器每0.5秒给客户端推送一次数据,每次推送的数据改变所有这1000个对象的位置。在这样的压力下,这个Demo的交互操作基本上没有延迟: <p>图 33 实时监控Demo <h6><a>4 符号扩展</a></h6> <p>在这个小节中,我们来看一下如何对符号进行扩展。虽然ArcGIS Flex API已经提供了非常多的符号供使用,但是如果你和我一样对符号有更多的需求,比如希望实现渐变效果、使用贝塞尔曲线来绘制符号等等,那么,这小节的内容对你将会有一些帮助。 <p>首先,让我们从一个点符号入手。一个点符号可以继承MarkerSymbol来扩展,其中最关键的在于重写MarkerSymbol的draw方法,这个方法负责绘制这个符号,下面让我们看一下这个draw方法的原型: <p><b>override</b> <b>public</b> <b>function</b> draw(sprite:Sprite, geometry:Geometry, attributes:Object, map:Map):<b>void</b> <p>这个方法带4个参数,其中sprite是Flash中一个轻量级的容器Sprite对象,用于在其上进行绘图;geometry是使用这个符号的几何对象;attributes是Graphic的属性值;map是Map组件的引用。 <p>绘制符号需要做两件事情:首先需要给sprite定位、定尺寸;然后再在sprite中绘制图形。下面的代码使用渐变填充绘制了一个圆形符号: <p><b>var</b> half:Number = size/2; <p>//定位、定尺寸 <p>sprite.x = toScreenX(map, mp.x); <p>sprite.y = toScreenY(map, mp.y); <p>sprite.width = size; <p>sprite.height = size; <p>//绘制图形 <p>sprite.graphics.beginGradientFill(GradientType.RADIAL, colors, alphas, ratios); <p>sprite.graphics.drawCircle(0, 0, half); <p>sprite.graphics.endFill(); <p>下面是这个扩展符号的效果: <p>图 34 渐变填充的扩展符号 <p>下面让我们再来看线符号的扩展,和点符号类似,线符号需要继承LineSymbol。实现的方法和上面类似,主要是重写draw方法,下面是我实现的一个绘制曲线行军箭头符号的效果: <p>图 35 扩展的行军箭头符号 <h5><a><b>三 </b><b>与其它系统的整合</b></a><b></b></h5> <h6><a>1 与JavaScript交互</a></h6> <p>Flex可以触发页面上的JavaScript函数;同时,页面上的JavaScript也可以触发Flex中的函数。因此,Flex可以和JavaScript双向交互,这使ArcGIS Flex API应用可以以非常独立的方式嵌入到某个页面。 <p>在我做的某个项目中,原先客户已经有自己的MIS系统,然后希望在其中加入GIS的功能。由于原来的MIS系统已经开发结束并具备了完整的功能,查询信息都能输出到页面的表格中,因此,给这个客户设计的解决方案就是采用ArcGIS Flex API,将GIS功能封装到Flex中,数据交互则通过JavaScript进行。 <p>我们从其中一个功能可以大致了解这种方案的实现和优点:刷新报警点。用户原先的报警点是输出到当前页面的表格中的,我们改写了JavaScript,使其在刷新报警点的时候,调用另外一个函数把这个表格中的所有数据发送到Flex中去,然后由一个GraphicLayer来负责绘制。 <p>下面是刷新报警点的JavaScript函数: <p><b>function </b>addPoints() <p>{ <p><b>var</b> map = document.getElementById("Map"); <p>map.clearHotPoints(); <p><b>var</b> table = document.getElementById("DataGrid"); <p><b>var</b> tBody = table.tBodies[0]; <p><b>var</b> rows = tBody.rows; <p><b>for</b>(<b>var</b> i=1; i<rows.length; i++) <p>{ <p><b>var</b> row = rows[i]; <p><b>var</b> attributes = <b>new</b> Array(); <p><b>for</b> (<b>var</b> j=0; j<row.cells.length; j++) <p>{ <p>attributes[j] = row.cells[j].firstChild.nodeValue; <p>} <p>map.addHotPoint(attributes); <p>} <p>} <p>在这个函数中出现了两个Flex中的方法,一个是在函数调用开始清空原先数据的clearHotPoints方法;另一个是添加报警点的addHotPoint方法。这两个方法都在Flex应用中如下定义: <p>private function init():void <p>{ <p><i>//</i><i>供JavaScript调用</i> <p>ExternalInterface.addCallback("clearHotPoints", clearHotPoints); <p>ExternalInterface.addCallback("addHotPoint", addHotPoint); <p>} <p>private function clearHotPoints():void <p>{ <p>this.hotPointLayer.clear(); <p>} <p>private function addHotPoint(attributes:Array):void <p>{ <p>var pt:MapPoint = new MapPoint(attributes[0], attributes[1]); <p>var g:Graphic = new Graphic(pt); <p>g.attributes = attributes; <p>this.hotPointLayer.add(g); <p>} <p>反过来,当用户在Flex中浏览到新的地图范围时,需要刷新数据以获得新的报警点,这是就需要从Flex调用JavaScript函数: <p>private function refresh():void <p>{ <p><i>//</i><i>调用JavaScript函数</i> <p>ExternalInterface.call("refreshHotPoints", xmin, xmax, ymin, ymax); <p>} <p>这个refreshHotPoints就是页面中负责刷新报警点的JavaScript函数: <p><b>function</b> refreshHotPoints(xmin, xmax, ymin, ymax) <p>{ <p>//原MIS系统的查询 <p>} <h6><a>2 HTTPService</a>与WebService</h6> <p>Flex通过HTTPService类从Web服务器获取数据,通过WebService类从Web Service获取数据。虽然这两个类与ArcGIS Flex API没有关系,但是,它们可以让你的Flex应用和原先可能已经存在的Web应用进行整合,融入除了ArcGIS Server提供的空间数据之外还可能存在的其它业务数据。 <p>HTTPService通过GET/POST方式访问某个URL来获取数据,数据的类型没有任何的要求,你只需要在Flex应用中监听响应事件,对响应的数据做相应的处理即可。 <p>下面的代码POST了一个点坐标到Web服务器保存,并对保存结果进行了处理: <p><mx:Script> <p><![CDATA[ <p><b>private</b> <b>function</b> addPoint():<b>void</b> <p>{ <p><b>var</b> mapPoint:MapPoint = graphic.geometry <b>as</b> MapPoint; <p><b>var</b> params:Object = <b>new</b> Object(); <p>params.x = mapPoint.x; <p>params.y = mapPoint.y; <p><b>this</b>.httpAddPoint.send(params); <p>} <p><b>private</b> <b>function</b> onAddPointResult(event:ResultEvent):<b>void</b> <p>{ <p><b>var</b> str:String = event.result <b>as</b> String; <p><b>var</b> json:Object = JSON.decode(str); <p>//对JSON数据的处理 <p>} <p>]]> <p></mx:Script> <p><mx:HTTPService id="httpAddPoint" url="{ROOT_URL+<b>'/point.php?action=add'</b>}" method="POST" showBusyCursor="true" result="onAddPointResult(event);" /> <p>HTTPService的url属性是GET/POST请求的地址,它可以是任何Web服务器的URL。在这里我使用了一个PHP应用,它的point.php页面中实现了添加一个点的x、y坐标到数据库的功能,当在Flex应用中使用HTTPService的send方法发送数据到这个页面上时,PHP应用会保存这个点到后台数据库,同时返回一个JSON格式的保存结果。Flex应用监听到这个ResultEvent以后就可以根据结果来进行不同的处理了。 <p>对于HTTPService来说,如果是从原有的Web应用上构建Flex应用,或许你还需要做些扩展,因为原先的Web应用可能输出的是HTML页面,但是Flex应用只需要XML、JSON等格式的数据;但是对于原有的Web Service则不存在这个问题,下面让我们看一下Flex中WebService类的使用: <p><mx:WebService id="ws" <p>wsdl="http://livecycledata.org/services/ProductWS?wsdl" <p>useProxy="false" showBusyCursor="true"/> <p><mx:DataGrid dataProvider="{ws.getProducts.lastResult}" > <p><mx:columns> <p><mx:DataGridColumn dataField="productId" headerText="编号"/> <p><mx:DataGridColumn dataField="name" headerText="名称"/> <p><mx:DataGridColumn dataField="price" headerText="价格"/> <p></mx:columns> <p></mx:DataGrid> <p><mx:Button label="获取产品信息" click="ws.getProducts()"/> <p>这里我们使用了一种不同的方式来处理服务器返回的结果&mdash;&mdash;数据绑定。虽然监听ResultEvent事件提供了很大的灵活性,但是对于很简单的业务逻辑(比如从服务器取数据填充到表格),我们也可以直接把HTTPService或者WebService的lastResult绑定到某个组件上(比如DataGrid)。上面的这个示例中,当我们点击按钮调用Web Service的getProducts操作后,服务器返回的最新数据就会自动绑定到DataGrid显示给用户&mdash;&mdash;这样的代码能让逻辑简单的业务显得十分优雅。 <p>通过HTTPService和WebService,我们可以很容易地在原有的Web应用基础上把功能移植或者添加到Flex应用中来,从而大大减少开发量。 <h6><a>3 RemoteObject</a>与BlazeDS</h6> <p>RemoteObject功能类可以通过AMF(Action Message Format)数据格式来访问远程应用服务器上的类(Java、.Net、PHP等)。AMF是一种压缩的二进制数据格式,专门用于ActionScript与服务器端通讯,其通讯效率比较高。总的来说,RemoteObject可以使你专注于Flex客户端开发和服务器端功能开发,而隐藏掉服务器提供Web功能的细节。 <p>在这个小节里我们主要针对Java来做详细的说明,因为Adobe公司针对Java发布了一个很好的开放源代码的服务组件BlazeDS,我们的Flex应用通过BlazeDS的帮助就可以无缝地调用远程服务器上的Java方法提供的功能。 <p>关于如何配置Flex应用使其支持J2EE,如何在其中加入BlazeDS支持,如何进行开发调试,在这里就不详细叙述了。我们这里主要了解Flex应用如何调用服务器端的Java方法及其效果。 <p>下面是一个从数据库获得污染节点并计算污染影响范围的例子。首先,我在服务器端编写了一个类Wind用以获得污染节点并通过风力参数来计算其影响范围: <p>package wuyf; <p>public class Wind <p>{ <p>private Connection conn = null; <p>public Connection getConn() <p>{ <p>if (conn==null) <p>{ <p>try <p>{ <p>Class.<i>forName</i>("org.postgresql.Driver"); <p>String url = "jdbc:postgresql://localhost:5432/sde" ; <p>conn = DriverManager.<i>getConnection</i>(url, "sde" , "sss" ); <p>conn.setAutoCommit(false); <p>} <p>catch(Exception e) <p>{ <p>System.<i>err</i>.print(e); <p>} <p>} <p>return conn; <p>} <p>public ArrayList<HashMap<String, String>> getEffectZones(<b>double</b> xMin, <b>double</b> yMin, <b>double</b> xMax, <b>double</b> yMax) <p>{ <p>ArrayList<HashMap<String, String>> result = new ArrayList<HashMap<String, String>>(); <p>if ( this.getConn()==null ) <p>return result; <p>try <p>{ <p>String sql = "select *&hellip;..from sde.wind"; <p>Statement st = this.getConn().createStatement(); <p>st.setFetchSize(0); <p>ResultSet rs = st.executeQuery(sql); <p>while (rs.next()) <p>{ <p>HashMap<String, String> map = new HashMap<String, String>(); <p>map.put("shape", rs.getString("ST_AsGeoJson")); <p>map.put("velocity", rs.getString("velocity")); <p>map.put("direction", rs.getString("direction")); <p>result.add(map); <p>} <p>rs.close(); <p>st.close(); <p>} <p>catch(Exception e) <p>{ <p>System.<i>err</i>.print(e); <p>} <p>return result; <p>} <p>} <p>要想这个类可以在Flex应用中通过RemoteObject可以访问到,我们需要在&ldquo;remoting-config.xml&rdquo;文件中添加这个类的定义: <p><destination id=<i>"Wind"</i>> <p> <properties> <p><source>wuyf.Wind</source> <p><scope>application</scope> <p></properties> <p></destination> <p>下面我们就想在Flex应用中调用Wind类的getEffectZones方法来得到污染影响范围,下面让我们来看一下Flex中相应的代码: <p><mx:Script> <p><![CDATA[ <p><b>private</b> <b>function</b> getEffectZones():<b>void</b> <p>{ <p><b>var</b> extent:Extent = map.extent; <p><b>var</b> args:Array = <b>new</b> Array(); <p>args.push(extent.xmin); <p>args.push(extent.ymin); <p>args.push(extent.xmax); <p>args.push(extent.ymax); <p><b>var</b> opt:AbstractOperation = <b>this</b>.roWind.getOperation(<b>"getEffectZones"</b>); <p>opt.addEventListener(ResultEvent.RESULT, onGetEffectZonesResult); <p>opt.arguments = args; <p>opt.send(); <p>} <p><b>private</b> <b>function</b> onGetEffectZonesResult(event:ResultEvent):<b>void</b> <p>{ <p><b>var</b> effectZones:ArrayCollection = event.result <b>as</b> ArrayCollection; <p><b>for</b> <b>each</b> (<b>var</b> effectZone:Object <b>in</b> effectZones) <p>{ <p><b>try</b> <p>{ <p><b>var</b> velocity:int = effectZone.velocity; <p><b>var</b> direction:int = effectZone.direction; <p><b>var</b> strShape:String = effectZone.shape; <p><i>//</i><i>根据结果生成Graphic</i> <p>windLayer.add(g); <p>} <p><b>catch</b>(err:Error) <p>{ <p><b>trace</b>(err); <p>} <p>} <p>} <p>]]-->

这里RemoteObject的destination属性对应的是在“remoting-config.xml”中配置的Java类的id,这时,你就可以近似把这个名为roWind的RemoteObject对象当成Wind这个Java类了。通过roWind.getOperation("getEffectZones")你就可以“获得”Wind类的getEffectZones方法(实际上是一个Flex中的AbstractOperation对象),这个方法可以是带参数的。最后,通过调用AbstractOperation对象的send方法,你就相当于调用了服务器端Wind类的getEffectZones方法,接下来的事情就是去监听相应的ResultEvent事件从而去处理服务器端Java类返回的结果了。

下面是通过RemoteObject调用该Java类并返回的结果:

图 36 RemoteObject的使用

4 通过BlazeDS调用AO

Flex应用是客户端应用,本身肯定是不能调用AO的,所谓调AO也就是将Flex端的数据发送到服务器上使用AO进行处理。我经常被问的一个问题就是:到底能不能用ArcGIS Flex API进行数据编辑,以前我一般回答不推荐使用Flex进行编辑。如果光从技术角度讲,我也曾经使用过WFS-T、GP服务、ADF等方法实现了编辑,所以实在要问我应该怎么在Flex中做数据编辑,我一般会说,那就自己做个Web应用去接收Flex发来的数据再解析、保存吧。

现在有了BlazeDS,上面的过程就可以被简化了。从上一小节我们已经知道如何使用BlazeDS来调用服务器端的Java方法,那么顺理成章,我们接着在这些方法中再调用AO[1] 就可以实现保存编辑等功能了。

比如我们在ArcGIS Flex API中使用Draw工具绘制了一个MapPoint,随即要把这个点保存到服务中,那么首先我们需要把这个MapPoint对象作为一个参数通过BlazeDS的AMF发送到服务器,在服务器端我们实现了这样一个Java类:

public class AGSServer

{

private IServerContext serverContext = null ;

public IServerContext getServerContext()

{

if ( serverContext==null )

{

try

{

ServerInitializer initializer = new ServerInitializer();

initializer.initializeServer("*", "wuyf", "pwd");

ServerConnection conn = new ServerConnection();

conn.connect("localhost");

IServerObjectManager som = conn.getServerObjectManager();

serverContext = som.createServerContext("Demo/demo", "MapServer");

}

catch (Exception ex)

{

ex.printStackTrace();

}

}

return serverContext;

}

public boolean addPoint(ASObject asObj)

{

boolean result = false ;

try

{

IServerContext serverContext = this .getServerContext();

IServerObject so = serverContext.getServerObject();

MapServer mapServer = (MapServer)so;

IFeatureLayer feaLayer =

(IFeatureLayer)mapServer.getLayer(mapServer.getMapName(0), 0);

IFeatureClass feaClass = feaLayer.getFeatureClass();

IFeature fea = feaClass.createFeature();

IPoint pt = AGSFlexToAOUtil.MapPointToAO(asObj, serverContext);

fea.setShapeByRef(pt);

fea.store();

mapServer.refreshServerObjects();

result = true ;

}

catch (Exception ex)

{

ex.printStackTrace();

}

return result;

}

}

上面的代码主要功能就是在我本机的“Demo/demo”这个地图服务中使用AO接口保存点要素。其中addPoint方法是保存点要素的主要方法,其参数asObj对应的是Flex中的MapPoint,asObj是一个ASObject对象,它是一个继承自HashMap的类,所有Flex中的结构都会被映射成一系列的键值对应,然后发送到服务器。我们根据自己的需要,通过对ASObject对象的解析就可以得到相应的AO对象了。

这里我还新建了一个AGSFlexToAOUtil类专门用于转化ASObject对象到AO对象,MapPointToAO方法就是负责转化Flex中的MapPoint到AO的IPoint的,这个方法的代码如下:

static public IPoint MapPointToAO(ASObject asObj, IServerContext scxt) throws Exception

{

Point pt = (Point)scxt.createObject(Point.getClsid ());

double x = Double.valueOf (asObj.get("x").toString());

double y = Double.valueOf (asObj.get("y").toString());

pt.putCoords(x, y);

return pt;

}

类似的,我还分别构造了PolylineToAO、PolygonToAO方法用以转化Flex传来的Polyline、Polygon对象为AO对象。下面是在Flex中对地图服务图层进行编辑并保存后的效果:

图 37 通过调用AO保存客户端编辑

II、 综合场景演示
1 某系统演示

下面是使用ArcGIS Flex API开发的一个输电输气管道监控管理系统的Demo,让我们试着从这个Demo去直观地了解在面对具体的业务时,ArcGIS Flex API可以发挥什么样的作用。

图 38 查看温度分布

图 39 降水、美国国家气象局警报

图 40 查看输气管道的泄漏情况

图 41 查看输气泄漏的统计情况

图 42 查看调度指令并可在新窗口中查看外部MIS系统

图 43 查看实时的道路交通情况

2 其它一些案例

[1] 注意在服务器应用中添加arcojects.jar包的支持

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值