2009年11月2日星期一

ArcGIS Server Java ADF 案例教程 v9.3.1 PDF 版本

地址:http://docs.google.com/fileview?id=0B8EMyhKZm53ZNmE3OTUzZjItODljNi00NDM3LWI2MGQtZTE1ZWNjMWRlMGY4&hl=zh_CN

2009年10月18日星期日

GIServer 介绍

GIServer起源于我在一个多月前,想重新捡起GeoServer做点东西。当时发现GeoServer的Style在使用上有点小问题;在通过bug tracker和GeoServer的开发人员沟通后,他们告诉我它本就是这样的——这让我不太能理解。同时,长久以来我都觉得GeoServer对OGC的那几个规范有着相当程度的痴迷——在某种程度上说,是对Web Service的崇拜。虽然我无意参与Web Service与REST的圣战,但是我也看到国内的一些GeoServer的使用者有时会问到这样一个问题:怎么使用ArcGIS Flex API调用GeoServer发布的服务?GeoServer只能告诉你,来用OpenLayers吧,我们才是黄金搭档。 因此,我开始实现一个类ArcGIS Server的GIS Server,并且首先针对REST接口进行实现,为的就是所有熟悉ArcGIS的JS/Flex/Silverlight API的人可以很容易上手来使用它。通过1个试验版本和3个alpha版本,目前map service和geometry service的功能都已经实现,你完全可以把它当成A

2009年9月9日星期三

ArcGIS Server Java ADF 案例教程 43

一 Server Object Extensions Server Object Extensions(以下简称SOE)是Server Object的扩展,比如MapServer、GPServer等等。和Utility Objects不同,SOE只在Server Object被创建的时候被初始化,它的生命周期和Server Object是相同的。相比Utility Objects,SOE有以下的一些特点: Ø 开发人员不需要手动实例化SOE,SOE是在Server Object被创建的同时被实例化的 Ø ArcGIS Server的管理程序可以显示SOE的参数配置 Ø SOE的运行信息将被记录到ArcGIS Server的日志中去 Ø SOE的功能可以通过SOAP访问 更直观一点,让我们看一下下面这张截图,这是一个添加了一个自定义SOE的Map Service管理界面: 图 30 添加了SOE的Map Service 是不是可以把SOE理解成一个服务的自定义Capability?似乎ESRI就是这样理解的。那下面我们就通过自定义一个新的Capability来看看SOE的用法。 二

2009年9月4日星期五

ArcGIS Server Java ADF 案例教程 42

二 使用Utility Objects提高查询性能【案例】 这是我的同事使用Utility Objects解决某个用户的一个实际问题。这个用户通过调用AO进行空间查询,然后在此基础上进行分页,分页的时候遍历该页所有的IFeature,提取属性信息,构造成自己的结果返回——结果,遇到了严重的性能问题,在上万条记录中查询一般两三分钟才可以出结果,这在一个国家级项目中是难以忍受的。 经过添加日志分析,发现AO查询的速度非常快,问题就是出在遍历IFeature这个操作上,是不停地进行跨进程调用产生的。就在这个时候9.3.1正式发布了,Utility Objects有如救星般地降临了! 由于在上一小节关于怎么定义Utility Object已经讲得比较清楚,这里就不在啰嗦怎么封装接口和实现类了。下面是封装起来的功能代码,主要就是遍历分页要素并提取有用的信息,当然,原先这部分代码是在Web层执行的。 IFeature feature = featureCursor.nextFeature(); int featureCount = 0; while (feature != null) { if (

ArcGIS Server Java ADF 案例教程 41

第一章 扩展ArcGIS Server 这一章其实和ADF已经没有太大关系了,在ArcGIS的帮助里也是在ArcGIS Server开发章节里。考虑到使用ADF开发的一般对功能要求比较多一点,所以对于怎么扩展ArcGIS Server可能比较关心,反正ADF差不多也讲完了,这一章就算是一个结束吧。 从9.3.1开始,ArcGIS Server提供了Java开发者更多的可扩展性。通过扩展ArcGIS Server可以使某些自定义的功能更加完善和强大、使你的某些工作可以更加模块化并和ArcGIS Server很好地结合在一起。下面让我们来看看ArcGIS Server可以通过什么样的途径进行扩展。 一 Utility Objects Utility Objects主要是为了提升ArcGIS Server在Local方式连接下某些操作的性能而准备的。在此之前,让我们首先去了解一些ADF调用服务器端AO对象的过程和特点。 图 29 ADF调用服务器端对象的过程 如图 29所示,当用户发生请求的时候,ADF首先会和SOM交互(图中1);SOM将根据服务器情况分配一个SO用以处理这个用户的请求

2009年9月2日星期三

ArcGIS Server Java ADF 案例教程 40

二 两种方式自定义Web Service 在IDE里创建一个Web Service工程是很容易的,只要找到【File】-【New】-【Web Service】菜单就可以了,点击这个菜单将会弹出下面的对话框: 这里的Web service type有两种选择:一种是Bottom up、另外一种是Top down。这两种方式分别对应了自下而上和自上而下两种新建Web Service的模式。如果你已经有了实现的功能类,现在想要把它封装成Web Service,那么你应当选择Bottom up模式;如果你首先定义了Web Service的描述文件(wsdl),现在想要实现这个wsdl的功能,那么你应当选择Top down模式。 三 实现Web Service的功能 不管通过Bottom up还是Top down方式,当你把Web Service的框架搭起来以后,剩下的工作就是如何去实现Web Service的功能了。下面让我们通过讲解一个网络分析Web Service的实现,来了解自定义Web Service的运作。 这个网络分析的Web Service需要的功能是预先定义好的,其中有比如

ArcGIS Server Java ADF 案例教程 39

第一章 有关Web Service 在IDE的菜单中,你会看到有个很长的【ArcGIS Web Services Project】,别高兴,这不是给你自定义Web Service的,运行完向导你会发现生成的是一个Java桌面程序——有着一如既往丑陋的界面和很不友好的GIS功能。原来我一直当它是废物,不过前段时间它还真的帮了我大忙。我们有个测试需要测ArcGIS Server对Web Service的支持,要求必须抓取到浏览器与服务器交互的可读的SOAP消息;但是你要知道,考虑到效率ArcGIS Server内部都是用二进制的SOAP进行交互;要抓到可读的SOAP消息,ArcGIS的Web Service包必须被部署到浏览器端——最后,就是靠这个丑陋的“ArcGIS Web Services Project”,做了很多修改封装成了Applet才达到了这个要求。 扯远了,说了这么多其实只为了说明这样一点:“ArcGIS Web Services Project”是一个使用ArcGIS的Web Service类库实现一些GIS功能的Java桌面程序,演示意义大于应用意义。下面我们首先来讨论一

2009年9月1日星期二

ArcGIS Server Java ADF 案例教程 38

三 调用AO实现缓冲功能【案例】 在这个小节中,让我们通过调用AO实现一个缓冲区分析的功能。 首先,我们需要配置一个地图服务资源,还是用前面使用过的世界地图服务吧,但是这次采用的是Local方式去连接,你可以直接在打开的JSP页面上右键选择【ArcGIS】-【Configure Map Service…】菜单打开服务配置窗口: 图 27 使用IDE插件配置 Local方式连接的服务 下面我们在一个Tool中通过这个AGSLocalMapResource来调用AO: public class BufferPointToolAction implements MapToolAction { public void execute(MapEvent event) throws Exception { WebContext webContext = event.getWebContext(); AGSLocalMapResource agsLocalMapResource = (AGSLocalMapResource)webContext.getResourceById("ags1"); I

2009年8月31日星期一

ArcGIS Server Java ADF 案例教程 36

调用ArcObjects 到这里我们对ADF已经讲了不少内容了,让我们先稍微回忆一下。我们从ADF的结构入手,首先介绍了Tool、Command和服务器的交互、在此基础上介绍了可以帮助实现复杂业务逻辑的Task;然后,进行了更细节的功能讲解,比如怎么进行查询、怎么显示动态结果、怎么不通过ADF组件来发送AJAX请求并获得响应、怎么自定义Functionality封装功能等内容。 前面这些内容仅仅围绕着ADF封装的GIS功能,或许你会开始怀念AO强大的功能,所以这一章就让我们来了解一下怎样可以用AO来进行更加底层的GIS功能实现。   一 Internet和Local方式连接服务的区别 首先让我们去了解一下在ADF应用中,连接服务的两种方式:Internet和Local的区别。 Internet方式通过一个URL来连接服务,它通过HTTP协议去访问ArcGIS Server的Web Service,这些Web Service会通过DCOM访问Server Object从而实现GIS的功能,这些都是ArcGIS Server封装好的,在使用的时候只需要知道服务的URL就可以了。由于这种方式

2009年8月5日星期三

ArcGIS Server 提升性能的主要要点

地图文档: 1.使用ESRI免费提供的mxdperfstat工具进行检查2.对简单应用使用msd3.不要使用动态投影 服务配置: 1.地图服务一般每个CPU核配2-4个实例2.GP服务一般每个CPU核配1个实例 地图切片输出格式: 1.主要由矢量数据构成的地图使用png2.主要由栅格数据构成的地图使用jpg ArcSDE: 1.栅格数据建议使用jpg而不是LZ772.数据不要注册版本 测试: 1.确保在测试前有个热身阶段2.缓慢增加用户压力

2009年7月9日星期四

为ArcGIS Server配置反向代理

一般来说,ArcGIS Server会位于Web服务器之后的本地网络,在生产环境中,这个网络一般是保密的、位于防火墙之后的,这也是ArcGIS Server不建议在本地网络中启用防火墙的原因之一。但是,出于心理上的慰藉或者稀奇古怪(抑或有不得已的成分)的部署方式,不启用防火墙(或者启用了防火墙但是要开放诸如8399等端口)有时让人无法接受;或者,还有ArcGIS Server还被部署在不同的网段中等各种情况——这种时候,反向代理就需要登场了。
 
比如ArcGIS Server的访问方式是http://:8399/arcgis/services/...,在配置反向代理后,你应该就可以通过http:///arcgis/services/...来访问ArcGIS的服务了。同时,在配置代理的PROXY_HOST主机上,你可以只开放80端口;而所有的ArcGIS_HOST主机都位于代理主机之后,Web服务器只通过代理主机去使用ArcGIS服务,用户没有任何机会去直接访问ArcGIS Server。好,现在你想怎么部署ArcGIS Server就可以怎么部署了,只需要告诉代理主机你的ArcG

2009年6月24日星期三

ArcGIS RIA 开发实践 PDF版本下载

鉴于直接贴出来的文档看不到图片,格式也比较混乱,在此提供一个PDF的下载: ArcGIS RIA 开发实践 PDF下载 另外一个下载地址

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

ArcGIS RIA开发实践 2

ArcGIS RIA开发实践【Flex篇】 I、 ArcGIS Flex API 基础 一 Flex的历史和现状 Flex的前身是Flash,Flash是极为流行的互联网矢量动画解决方案,目前据统计97%的浏览器都安装有Flash Player插件用以播放swf动画,其中未安装的3%还有很多是因为某些场合的安全限制导致的,可以说Flash是极为普及的RIA平台。 但是由于Flash是一个动画制作软件,其中有很多比如时间轴、影片剪辑等程序员不容易理解的概念,因此Macromedia公司推出了Flex。Flex抛弃了许多动画设计术语,转而使用程序员喜欢的方式开发RIA应用,并且Flex能编译生成可以在Flash Player中运行的swf文件,这无疑吸引了大量程序员,并且实现了和Flash平台的无缝拼接,从而利用Flash平台多年积累的大量素材、美工和设计者。 Adobe公司在2005年收购了Macromedia公司,并在第一时间将Flex/Flash冠以Adobe的商标推向市场,可见其对RIA市场和Flex/Flash的重视。 目前,可以说Adobe Flex/Flash是最流行且最成熟

ArcGIS RIA开发实践 1

ArcGIS RIA开发实践【背景篇】 I RIA介绍 一 RIA相比传统Web应用的优势 RIA作为一个互联网领域越来越火的术语,究其根本在于它相对传统Web应用的诸多优势。RIA作为“富互联网应用”的代名词(Rich Internet Application),从名字上就已经可以看出它最大的特点:Rich,这个Rich是多方面的,包括丰富的表现力、富有交互、内容丰富、基于富客户端引擎等等。同时,RIA是Internet的,它在功能上希望和传统的Web应用一样通用、标准,因此,这也是为什么眼下很多2D/3D效果俱佳、操作人性化的网络游戏不能称为RIA的原因。 传统Web应用的很多不足都来源于HTML的静态性,虽然由于JavaScript脚本的存在可以在浏览器中实现一些客户端交互和效果;同时,Web2.0浪潮推广的AJAX技术也使浏览器和服务器的通讯更及时、页面表现也更友好。但是,传统Web的根基使这些进步都是局限的,在声音、视频、Socket通讯、本地存储这些方面它都捉襟见肘。 RIA则在某种程度上颠覆了传统Web应用的理念和设计。为了能提供客户端更优秀的表现和交互能力,RIA一般会

ArcGIS Server Java ADF 案例教程 35

二    自定义一个地图放大5倍的Functionality【案例】在这个小节,我们去实现一个GISFunctionality去封装前面实现过的使地图放大5倍的功能。首先,当然是新建一个Functionality类并添加到ADF应用中去。前面我们经常通过直接编辑代码的方式去定义类和配置文件,这主要是为了帮助你理解ADF应用的配置和各个元素的相互关系。现在估计你也有一定了解了,我们也就尽量省事些,多使用ArcGIS的eclipse插件去完成这些工作吧。让我们打开eclipse的菜单【ArcGIS】-【Class Templates】-【Functionality】,在新建的窗口上填上我们希望实现的Functionality的包名、类名、实例化 Managed Bean的名称、注册到的Resource等,完成以后ADF会自动新建一个实现GISFunctionality接口的类,并把这个类实例化后注册到 ADF应用的配置文件中去。 图 26    通过ArcGIS的IDE插件添加自定义Functionality 下面我们需要对新生成的这个Functionality类进行编码以实现自己的功能

ArcGIS Server Java ADF 案例教程 33

自定义AJAX请求获得地图范围【案例】比如你现在想随时知道当前地图的边界范围,也就是说每次地图范围更新后,希望服务器可以输出一个包含边界范围的AJAX响应到浏览器,那么首先你需要在用户对地图进行操作的时候发送一个AJAX请求。这个请求你可以在每次地图被更新的时候去调用,比如一开始就通过addUpdateListener给 Map添加一个监听:function onBodyLoad(){    map = EsriControls.maps['map'];    map.addUpdateListener("onMapUpdate", onMapUpdate);}function onMapUpdate(){    var formId = map.formId;    var url = EsriUtils.getServerUrl(formId);    var params = "getMapInfo=true&" + EsriUtils.buildRequestParams(formId);    EsriUtils.sendAjaxRequest(url, params, tr

ArcGIS Server Java ADF 案例教程 32

监听AJAX请求并响应当从页面使用JavaScript发送了一个AJAX请求到服务器,服务器就需要对这个请求进行处理,因此这一小节的内容主要就是在服务器端。首先,让我们来了解一些JSF的内容——有关PhaseListener 。所谓的Phase就是JSF请求的阶段,一个JSF请求的生命周期中存在不同的阶段,当用户从页面向服务器发送一个请求的时候,JSF请求的生命周期就开始了。以下就是著名的JSF请求生命周期中6个阶段: 图 23   JSF请求的生命周期 PhaseListener是对JSF请求的阶段进行监听的接口,当某个类实现了这个接口并添加到JSF应用中以后,每当服务器接收到JSF请求,并处理这个请求到某个阶段的时候,这个监听类就会被触发。下面是实现的一个监听自定义AJAX请求的监听类:public class MapUpdatePhaseListener implements PhaseListener{    public void afterPhase(PhaseEvent event)    {        FacesContext facesContext = eve

ArcGIS Server Java ADF 案例教程 31

自定义的请求和响应 前面用户的请求都是通过Task、Toolbar来执行的,在这一章里,我们将不使用这些ADF组件,来看看没有了它们的帮助,你应该怎么自己发送AJAX请求,或者在原先输出的AJAX响应中添加上一些额外的信息。 一 发送AJAX请求 首先让我们看一下怎么不依靠任何ADF组件的帮助来发送一个AJAX请求,比如,在页面上有一个纯HTML的表单按钮,我们希望点击这个按钮的时候可以发送一个AJAX请求到服务器,应该怎么做? 那就让我们假设这样一个场景:用户点击一个HTML的“放大5倍”按钮,地图就可以放大5倍。要实现这样的功能,当然首先是需要添加一个按钮,这个按钮的onclick事件对应到一个JavaScript函数: function mapZoom() { var formId = map.formId; var url = EsriUtils.getServerUrl(formId); var params = "mapZoom=true&" + EsriUtils.buildRequestParams(formId); EsriUtils.sendAjaxRequest

ArcGIS Server Java ADF 案例教程 28

在上一小节最后说马上要讲Graphic,为了和ADF本身的称呼统一,现在这里我们将其称为GraphicElement。由于Blogger被河蟹了,以前的文章我就先不修改了,下面我们来谈一下GraphicElement。 GraphicElement 要说GraphicElement就得先说WebGraphics,欲用GraphicElement,必先添加WebGraphics,在你的配置文件中你应该可以找到如下的定义: graphics com.esri.adf.web.data.WebGraphics none 这个WebGraphics对象的作用就是在服务器端存放所有你需要动态表示的GraphicElement。当然,现在我们已经很熟悉应该怎么去调用WebGraphics对象了:首先肯定要得到WebContext对象,然后在它的attribute中找到WebGraphics对象。 在WebGraphics对象中添加GraphicElement以后,这些GraphicElement就可以自动在Map中渲染出来,它们和后台服务没有关系,WebGraphics中的对象会在后台服务的上

ArcGIS Server Java ADF 案例教程 29

GraphicElement的符号化GraphicElement的symbol属性是一个WebSymbol 对象,这个对象定义了使用什么符号来表示GraphicElement对象。下面让我们大概看一下有些什么WebSymbol可以为我使用: WebSimpleMarkerSymbol 简单点符号WebSimpleLineSymbol 简单线符号WebSimplePolygonSymbol 简单多边形符号WebPictureMarkerSymbol 图片点符号WebTrueTypeMarkerSymbol TrueType点符号 比如一个简单点符号可以这样定义:WebSimpleMarkerSymbol symbol = new WebSimpleMarkerSymbol();symbol.setMarkerType(WebSimpleMarkerSymbol.STAR);symbol.setAntialiasing(true);symbol.setTransparency(0.7);symbol.setColor("255,255,0");symbol.setOutlineColor(

在Google blogger的日志备份5

02/11/2009 03:59 PM ArcGIS Flex API 的动态投影 以前没关注过这个问题,经过同事提点REST API中有指定投影的请求参数,猜想Flex中也可以。方法如下:url="http://sampleserver1.arcgisonline.com/ArcGIS/rest/services/Demographics/ESRI_Census_USA/MapServer"/>url="http://sampleserver1.arcgisonline.com/ArcGIS/rest/services/Petroleum/KGS_OilGasFields_Kansas/MapServer"/>也就是在加载图层的时候,指定Map的Extent(这个Extent通过wkid来指定其空间参考),得出的图则是该空间参考下服务器动态投影生成的图片。下面两图分别是wkid=26777和wkid=4326下同一地图服务的不同表现: 02/04/2009 05:08 PM 免费的矢量导航数据 做GIS很重要的就是数据,事实上这个玩意也很值钱。有时候你想做个演示或者要节省项目成本

在Google blogger的日志备份4

12/11/2008 10:04 AM 使用 FlashDevelop 开发 ArcGIS Flex 应用 【环境】Windows XP SP3,FlashDevelop 3.0.0 Beta9,Flex SDK 3.2,ArcGIS Flex API 1.0如果你对基于Eclipse的Flex Builder的编译和操作不甚满意,常常为它的无端崩溃而心怀不满,更或者,你不倾向于使用收费的Flex Builder(标准版300多美刀),那么,或许你可以尝试一下这个主题的主角——一款开源的Flex IDE:FlashDevelop。FlashDevelop使用MIT许可分发,拥有和Visual Studio风格类似的界面和操作,还拥有插件机制来扩展功能——总而言之,它可以成为FlexBuilder之外开发Flex应用的又一选择。我们可以从 http://www.flashdevelop.org 获取FlashDevelop的安装文件和文档。当你安装Flex Builder的时候,或许你不用操心任何其它组件,包括Flex SDK和Flash Player Debug版本等等都内置在其中。

在Google blogger的日志备份3

10/12/2008 09:46 PM Open API 其实大家都希望开放自己的API以后,吸引开发人员在自己的平台上做点事情。吹得美妙无比,内心都险恶得要死巴不得天底下别的API都死绝了,大家都用我的吧。Open API是一个比云更ws的概念,它的口号就是:我的是我的,你的还是我的。小子,我这里有全套工具,来给我打工吧!好,你就瞎忙活去吧。你要用它的API在它那里实现了什么功能,恭喜你,你已经正式成为了它的免费兼职实习生;你要用它的API在你自己那里使用了什么功能,给它打了广告不说,啥时候你的流量大了,对不起,从今天开始限制访问量,非付费用户每天限制IP500个,不够?交钱啊。这叫什么,消费陷阱啊。所以啊,云啊雾啊开放啥的都是虚的,数据才是根本。有了数据你才有发言权,你才有资本去做云啊雾的,然后对大家招招手说:hey come on,给你露几个接口,用去吧。ok,到时候你也会说:Open API好,云实在是好。 09/18/2008 09:08 AM Android开发简明教程-1.简介 【关于本教程】Android的API文档和APIDemo做的是很不错的,但是Tutori

2009年6月23日星期二

在Google blogger的日志备份1

08/01/2008 09:22 AM 配置ArcGIS Server 9.3 Java开发环境(NetBeans) 【概述】叙述如何在Window下配置ArcGIS Server 9.3的Java开发环境(NetBeans) 【环境】Windows XP SP2,ArcGIS Server 9.3,NetBeans 6.1 先决条件 ArcGIS Server的Java开发首先需要安装Java Web服务器, ArcGIS Server 9.3支持的服务器种类有:Apache TomcatIBM WebSphereJBoss 这里采用的是Tomcat 6.0.16。 下载并安装NetBeans ArcGIS Server 9.3支持的NetBeans版本为6.x,可以从NetBeans网站免费下载:http://download.netbeans.org/netbeans/6.1/final, NetBeans下载包的类型建议选择Web&J2EE;(第一列)。 在开始安装NetBeans时,首先会让你选择是否安装随NetBeans附带的应用服务器——GlashFish或Tom

2009年5月26日星期二

不稳定的Blogger

如果2009.6.1之前不恢复的话,我就换个地方吧

作坊暂定搬到:http://hi.baidu.com/wuyf0330

2009年5月14日星期四

ArcGIS Server Java ADF 案例教程 27

四 QueryResult

上面几个小节让我们知道了不同的查询功能应该怎么去实现,每个查询中都返回了QueryResult对象的集合。这个小节我们就来详细说说QueryResult对象。

QueryResult对象是一个查询结果,当然,这是一个服务器端对象,然而在服务器上QueryResult并不仅仅是一个记录,而是一个可以和Map互动,可以高亮显示、移除、缩放到的一个对象。

比如当我们做完查询以后,想在地图上显示所有的结果要素,那么,调用QueryResult的highlight方法就可以做到:
webContext.getWebGraphics().clearGraphics();
for (int i = 0; i <>
{
QueryResult queryResult = listQueryResult.get(i);
queryResult.highlight();

}
webContext.refresh();


面的代码遍历了查询结果中所有的QueryResult对象,调用每个对象的highlight方法使其在地图上高亮显示,让我们看一下执行的结果:
图 18 高亮查询结果

高亮的这些元素事实上是将这些查询结果以Graphic的形式添加到Map中的。ADF的Graphic通常用于表现临时的、动态生成的业务数据,下面一章我们会马上去看Graphic的来龙去脉。

ArcGIS Server Java ADF 案例教程 26

三 Where条件查询

三个实现了QueryCriteria接口的类我们还剩PredefinedQueryCriteria没有介绍,这个类是用于进行where查询的。在进行查询的时候,通常需要设置一个比较复杂的过滤条件,这里就需要PredefinedQueryCriteria。

下面我们尝试把前面使用TextCriteria查询的方法用PredefinedQueryCriteria来实现:
WebQuery webQuery = (WebQuery) webContext.getAttribute("query");

PredefinedQueryCriteria criteria = new PredefinedQueryCriteria();
criteria.setWhereClause("CITY_NAME like '%"+this.keyWord+"%'");

List
listQueryResult = webQuery.query(criteria, webQuery.getQueryLayers());

PredefinedQueryCriteria的用法非常简单,无非是把你想要设置的where条件放到它的whereClause属性中去,这里就不赘述了。

ArcGIS Server Java ADF 案例教程 25

二 几何对象查询

在上一章的QueryTask中也有对几何对象的查询,你可以回忆一下那个Task面板中“选择”那个Tool。这个Tool会在Map中绘制一个多边形,然后ADF根据这个多边形查询位于多边形内的所有要素。

几何对象的查询主要使用IdentifyCriteria对象,下面的代码实现了这样一个根据多边形查询要素的功能:
WebContext webContext = event.getWebContext();
WebGeometry webGeometry = (WebPolygon) event.getWebGeometry().toMapGeometry(webContext.getWebMap());

WebQuery webQuery = (WebQuery) webContext.getAttribute("query");
List
layers = webQuery.getQueryLayers();
List
queryLayers = new ArrayList();
for (WebLayerInfo layer : layers)
{
if (layer.getName().equals("World Cities"))
{
queryLayers.add(layer);
break;
}
}

IdentifyCriteria identifyCriteria = new IdentifyCriteria();
identifyCriteria.setWebGeometry(webGeometry);

List
listQueryResult = webQuery.query(identifyCriteria, queryLayers);

在这个查询过程中,首先从MapEvent事件中获得从客户端传来的WebGeometry对象(这里是用户在客户端的Map中画的多边形),这个对象是基于客户端坐标的,因此需要转化成地图坐标。由于我们只想查“World Cities”这个图层上的要素,因此下面又构造了一个queryLayers,将我们想要查询的图层挑选了出来。在此之后,我们就可以使用IdentifyCriteria对象,将ADF转化成地图坐标的WebGeometry对象作为它的参数,进行查询得到结果。

2009年5月13日星期三

Flex中实现行军箭头的效果

2009年5月8日星期五

ArcGIS Server Java ADF 案例教程 24

第六章 查询及结果

对空间信息进行查询是GIS中很基本且很常用的功能,在这一章中,我们主要看在ADF中如何进行查询、查询出结果以后怎样返回客户端。另外,在这里也先预告下,在下一章我们还会讲查询到的这些结果怎样在Map中表现出来。

一 文本查询

在上一章QueryTask的query方法中我们已经用到了文本查询的功能,你可以回忆一下,在这个方法中我们根据Task面板中文本框的内容,对图层进行了查询,用到的就是keyWord这个字符串变量。下面我们来详细说一下文本查询的过程。

在ADF中负责进行文本查询的是WebQuery对象,需要用到查询功能的ADF应用都需要在配置文件中加上了这个对象:

当我们需要使用查询功能的时候,首先第一步就是获得这个WebQuery对象,当然,通常都是先获得WebContext对象,再查找它的attribute来获得WebQuery对象:
WebQuery webQuery = (WebQuery) webContext.getAttribute("query");

通过这个WebQuery对象我们就可以开始进行查询了,WebQuery最重要的方法就是query方法,让我们先看一下query方法的原型:

这个query方法主要有两个参数:criteria参数表示查询条件,这是一个实现了QueryCriteria 接口的对象,实现这个接口的类有三种:IdentifyCriteria, PredefinedQueryCriteria, TextCriteria,分别对应不同的查询类型,在这里我们将要用到TextCriteria这个类;query方法的另外一个参数queryLayers表示需要进行查询的图层,一个服务中可能有多个图层,这个参数可以限制查询的范围。

上面说到进行查询需要一个实现了QueryCriteria接口的对象作为查询条件,这里我们需要查询文本,所以需要使用TextCriteria,这是一个专为文本查询设计的类。下面的代码定义了一个TextCriteria对象,通过这个对象定义了一些查询条件和限制,最后通过WebQuery对象的query方法来得到查询结果:

这是一个很简单的查询,事实上TextCriteria还可以设置很多限制和条件,比如限制查询字段、是否模糊查询、设置返回字段、设置LayerDefinition (包括字段别名、符号渲染方式)等等。最后,查询后会得到一个QueryResult的集合,关于这个QueryResult的特点和用法,我们在后面会有一个小节专门说明。

2009年5月7日星期四

刚做的H1N1猪流感分布图Demo

主要是HeatMap的实现,原来网上有人发了一个,不过我去看的时候已经因为没有得奖的原因把源码给撤了……还是自力更生吧。

点击这里查看在线演示

这是大概的效果图:

2009年5月4日星期一

使用pgRouting进行路径分析

pgRouting是一个基于PostgreSQL/PostGIS的项目,目的是提供路径分析的功能,它是PostLBS的一个子项目,这个项目使用GPL许可发布。

pgRouting的安装很简单,以Windows为例,下载编译包以后解压缩,将lib目录下文件复制到PostgreSQL的lib目录下,再在PostgreSQL数据库中执行share/contrib目录下的sql脚本,这些脚本分别对应不同的功能:“core”对应Dijkstra算法计算最短路径,使用函数为“shortest_path_*”;“dd”对应Driving Distance行驶距离计算,使用函数为“driving_distance”;“tps”对应采用遗传算法的Travelling Sales Person方法,使用函数为“tsp_*”。

下面用日本神奈川的城市道路数据进行测试。

计算最短路径我们使用shortest_path这个函数,这个函数需要提供5个参数,下面是shortest_path的函数原型:
shortest_path(sql text, source_id integer, target_id integer, directed boolean, has_reverse_cost boolean)

这里的sql是一个sql语句,通过这个sql语句可以获得需要计算的数据集合;source和target分别是起始节点的id;directed表明是否限制方向。需要说明的是这个sql语句,在这个sql中需要查询到一些特殊的字段:id、source、target、cost等,而且这些字段的类型必须和pgRouting的要求相符。下面是我构造的一个查询:
SELECT * FROM shortest_path(' SELECT objectid as id, source::integer, target::integer, length::double precision as cost FROM kanagawa', 84808, 13234, false, false);

在本机上测试,上述查询在19万行数据中执行了2秒得到结果318行(路径分为318段):

以下是服务器端的主要代码:
public ArrayList> getNodes(int source, int target)
{
ArrayList> result = new ArrayList>();

if ( this.getConn()==null )
return result;

try
{
String sql = "SELECT * FROM shortest_path('SELECT objectid as id,source::integer,target::integer,length::double precision as cost FROM kanagawa', "+source+", "+target+", false, false) as a left join kanagawa as b on a.edge_id=b.objectid;";

Statement st = this.getConn().createStatement();
st.setFetchSize(0);
ResultSet rs = st.executeQuery(sql);
while (rs.next())
{
HashMap map = new HashMap();
map.put("x1", rs.getDouble("x1"));
map.put("y1", rs.getDouble("y1"));
map.put("x2", rs.getDouble("x2"));
map.put("y2", rs.getDouble("y2"));
result.add(map);
}
rs.close();
st.close();
}
catch(Exception e)
{
System.err.print(e);
}

return result;
}

2009年4月30日星期四

ArcGIS Server Java ADF 案例教程 23

三 Task的渲染

Task的功能类中还有一个属性,IDE默认生成的名字是taskInfo,它对应这个另外一个Java类(如上面QueryTaskTaskInfo类)的对象。这个类主要控制Task的渲染,我们可以回想一下,如果光在Task的功能类中定义了Command是一个void类型的query方法,Task面板怎么会生成一个标签为“查询”的按钮出来的呢?答案就在taskInfo中。

打开上面生成的“QueryTaskTaskInfo.java”文件,我们可以看到以下的内容:
public class QueryTaskTaskInfo extends SimpleTaskInfo
{
public TaskDescriptor getTaskDescriptor()
{
TaskDescriptor descriptor = new TaskDescriptor(QueryTask.class, "queryTask", "查询任务");
return descriptor;
}

public TaskParamDescriptorModel[] getParamDescriptors()
{
TaskParamDescriptor[] descriptors = new TaskParamDescriptor[1];
descriptors[0] = new TaskParamDescriptor(QueryTask.class, "keyWord", "关键词", "getKeyWord", "setKeyWord");
return descriptors;
}

public TaskActionDescriptorModel[] getActionDescriptors()
{
TaskActionDescriptor[] descriptors = new TaskActionDescriptor[1];
descriptors[0] = new TaskActionDescriptor(QueryTask.class, "query", "查询");
return descriptors;
}

public TaskToolDescriptorModel[] getToolDescriptors()
{
TaskToolDescriptor[] descriptors = new TaskToolDescriptor[1];
descriptors[0] = new TaskToolDescriptor(QueryTask.class, "select", "选择", "EsriMapPolygon");
return descriptors;
}
}


简单的说,TaskInfo类中包含4个主要的属性:TaskDescriptor对象、TaskParamDescriptor[]数组、TaskActionDescriptor[]数组、TaskToolDescriptor[]数组。很好理解,TaskDescriptor对应整个Task的外观,比如Task的标题等;TaskParamDescriptor[]描述所有Parameter的外观;TaskActionDescriptor[]描述所有Command的外观;TaskToolDescriptor[]描述所有Tool的外观。一个Task中肯定包含不定数目的Parameter、Command或Tool,因此他们的描述需要数组来存放。

让我们通过TaskToolDescriptor来更详细地看一下,上面的代码设置“select”这个TaskTool表现为一个“选择”按钮,并在点击时通知Map执行“EsriMapPolygon”操作。现在我想让它渲染成一个自定义的图片按钮,并随鼠标移上或点击切换不同的图片,用下面的代码可以实现这样的功能:
descriptors[0].setRendererType(TaskToolDescriptor.IMAGE_RENDERER_TYPE);
descriptors[0].setDefaultImage("images/tasks/maptools/polygon.gif");
descriptors[0].setHoverImage("images/tasks/maptools/polygonU.gif");
descriptors[0].setSelectedImage("images/tasks/maptools/polygonD.gif");
descriptors[0].setDisabledImage("images/tasks/maptools/polygonX.gif");


现在我还想调整一下这些按钮、文本框的布局,怎么办?TaskInfo还可以指定Task各个子元素的布局,让我们在刚刚的QueryTaskTaskInfo类中再添加以下的代码:
public TaskLayout[] getTaskLayout()
{
TabularLayout[] descriptors = new TabularLayout[1];
descriptors[0] = new TabularLayout();
descriptors[0].addComponent(getParamDescriptors()[0], new TabularPosition(0, 0, 0, 0));
descriptors[0].addComponent(getActionDescriptors()[0], new TabularPosition(1, 0, 0, 0));
descriptors[0].addComponent(getToolDescriptors()[0], new TabularPosition(2, 0, 0, 0));
return descriptors;
}


经过上面的两次修改,我们会发现Task面板的Tool变成了使用图片渲染,并且布局方式更加我们的定义进行了变化,效果如图 17所示。
图 17 更改Tool渲染方式和布局后的Task面板

顺便回头再看看页面中Task的定义,其中有taskInfo属性,它对应的就是这个Task的TaskInfo对象:
taskInfo="#{queryTask.taskInfo}"

最后稍微总结一下吧,Task的外观主要通过TaskInfo来定义,ADF会根据Task所对应的TaskInfo,通过XSL样式文件渲染成DHTML代码,并输出到浏览器。到现在为止,一个Task面板包含什么内容、对应后台什么对象、怎么控制它们的表现,你应该都清楚了吧?

ArcGIS Server Java ADF 案例教程 22

二 Task的执行

Task的执行主要在对应的功能类(比如上面的QueryTask类)中实现,上面已经详述了Task中的Parameter、Command、Tool分别对应的功能类中的元素,下面就让我们看看怎么去使用它们。

在上面定义的那个QueryTask中,我们设计点击“查询”按钮就能根据文本框中输入的关键词去查找相应的要素。下面是实现这样功能的一段代码:
public void query(TaskEvent event)
{
WebContext webContext = event.getWebContext();

WebQuery webQuery = (WebQuery) webContext.getAttribute("query");
TextCriteria textCriteria = new TextCriteria();
textCriteria.setMaxRecordCount(100);
textCriteria.setSearchText(this.keyWord);

List
listQueryResult = webQuery.query(textCriteria, webQuery.getQueryLayers());

webContext.getWebGraphics().clearGraphics();
for (int i = 0; i <>
{
QueryResult queryResult = listQueryResult.get(i);
queryResult.highlight();
}

webContext.refresh();
}

我们可以看到,从TaskEvent中可以直接获得我们的WebContext对象;从WebContext开始可以执行一系列的操作,其中用到了keyWord这个属性,keyWord对应的就是用户在Task面板的文本框中输入的文字;最后查询到结果在地图上通过Graphic高亮出来,通过WebContext的refresh方法输出到浏览器。

这里涉及到了Parameter和Command的使用;至于Tool,唯一的区别就是WebContext是从MapEvent中获得,因此这里就不详细写了。另外,这段代码中还涉及到如何进行查询、如何使用Graphic等,这些内容我们在后面会详细讲。

2009年4月29日星期三

ArcGIS Server Java ADF 案例教程 21

这一章我们主要研究一下Task同志的成份和出身。ADF中本身包含一些现成的Task,不过相比对开发人员来说没有很大的意义,这一章我们的重点还是在如何自定义Task。

Task是实现业务逻辑的重要部分。简单来说,Task可以让输入一些参数(在页面上以文本框、组合框等形式出现)、通过Command和Tool的组合,帮助你完成特定的“任务”并返回结果,这就是所谓的Task。

一 Task的请求

虽说这一节的标题是Task的请求,但在这里我们对ADF的Task怎么向服务器做请求并不作深入的探讨,这里我们主要是去了解一个Task在服务器端执行的时候需要客户端提供的信息有哪些。
图 13 一个Task的请求面板

如图 12是一个简单的Task请求面板,在这个面板中会有一些Parameter(一般以文本框、组合框等形式出现)和一些Command或Tool(一般以按钮形式出现)。下面我们先来定义一个Task,其中包含一个Parameter、一个Command和一个Tool。

在IDE中新建一个Task最简单的方法就是使用菜单【File】-【New】-【Task】,然后给你的Task起个名字,如图 14。
图 14 新建一个Task

这个时候,IDE会弹出另外一个对话框供你对这个Task进行一些设置,比如Task的名称、里面包含的Parameter、Command(在这里叫Action)、Tool等。这里就根据你的需要进行填写了,我们先各添加一个,如图 15所示。
图 15 给Task添加一些元素

这时你会发现,在Java源文件目录下多了2个文件,一个是“QueryTask.java”,另一个是“QueryTaskTaskInfo.java”,同时在faces-config.xml文件中也多了一个名为queryTask的ManagedBean。

打开“QueryTask.java”文件,我们可以找到和刚才定义的那些内容相对应的代码,下面的表简述了这样的一种对应关系:

类型 名称
Parameter String keyWord
Command Void方法 query(TaskEvent event)
Tool Void方法 select(MapEvent event)

在“QueryTask.java”中我们还可以找到QueryTaskTaskInfo对象,关于QueryTaskTaskInfo这个类的定义及其作用我们在后面再详细说。下面让我们先把这个Task添加到页面上去:

这里的taskInfo属性就是这个Task的TaskInfo对象,我们可以先不用管,唯一一个以前没有出现过的属性就是windowingSupport,这个属性定义的是这个Task面板是否可以在页面上浮动并拖拽。让我们运行下这个页面看一下效果:
图 16 上面定义的Task面板的效果

稍微总结一下,Task中主要包含3个功能元素:Parameter、Command、Tool;Task对应的Java类有2个,其中在实现功能的Task类(比如上面的QueryTask类)中,Parameter对应类中的属性字段、Command对应类中的带TaskEvent 参数的void方法、Tool对应类中的带MapEvent 参数的void方法。这样,前台的Task请求就和后台具体的功能联系起来了。

2009年4月28日星期二

ArcGIS Server Java ADF 案例教程 20

七 自定义Button实现前面的Command和Tool【案例】

在这一章前面的小节中,我们实现了两个案例分别演示Command和Tool的功能;在这个案例里,让我们用Button来完成相同的工作。
首先,我们需要在一个我们自己定义的ManagedBean 来实现这些功能。注意,在这个ManagedBean中我们会使用WebContext对象(否则怎么调用地图功能),因此,我们这个类定义需要实现WebContextInitialize接口。当然,在IDE中有更简单的方法,比如在Eclipse中选择菜单【New】-【Context Attribute】就可以新建一个ManagedBean并添加到WebContext对象的attributes中去。
好了,现在我们已经有了自己的ManagedBean类“MapEditor”;接下来我们先实现地图等比放大的功能。在这个类中,我们添加一个不带任何参数的方法zoomIn,在zoomIn实现地图放大一倍的功能。源代码如下:
package wuyf;

import com.esri.adf.web.data.WebContext;
import com.esri.adf.web.data.WebContextInitialize;

public class MapEditor implements WebContextInitialize
{
private WebContext webContext;

public void destroy() {}

public void init(WebContext context)
{
this.webContext = context;
}

public void zoomIn()
{
webContext.getWebMap().getCurrentExtent().expand(0.5);
webContext.refresh();
}
}

在页面中用如下的标签进行定义:

这个时候,你在页面上点击这个Button的时候,地图也会放大一倍了。
下面我们接着去实现点击地图居中,和上面类似,只不过实现这个功能的方法需要带一个MapEvent参数:
public void centerAt(MapEvent event)
{
WebPoint webPoint = (WebPoint)event.getWebGeometry().toMapGeometry(webContext.getWebMap());
webContext.getWebMap().centerAt(webPoint, 1.0);
webContext.refresh();
}

在页面中定义的标签则如下:

好了,试着点击这个按钮,再在地图上点击,是不是地图以点击处居中了?

ArcGIS Server Java ADF 案例教程 19

六 Button

Button不属于Toolbar的子元素,而是和Toolbar有类似的地位;但同时,它的功能又集合了Command和Tool。Button的出现主要是为了方便用户,当用户不希望出现一个工具条来摆放与地图相关的功能,那么Button可以在页面的任意地方摆放一个与地图相关的按钮。

在ADF工程里,让我们打开ADF自带的模板页面“edit.jsp”,在里面有很多Button。以下是定义了一个在客户端绘制一个矩形,服务器端执行“选择要素”操作的Button:

我们可以看到,Button和Command以及Tool都很相像,有clientAction也有serverAction,所不同的是,Button需要指定一个mapId属性,因为Command和Tool都是存放在Toolbar中,Toolbar会有个统一的mapId来表示和哪个Map绑定;而Button则由于是独立的按钮,需要自己指定与之绑定的Map。

clientAction属性是可选的,如果你不填clientAction,那么这个Button的行为就类似于Command;如果你设置了clientAction,那么这个Button的行为就类似与Tool。另外,这里的serverAction需要指向一个服务器端对象的方法,如果没有clientAction,那么这个serverAction指向的方法应该是一个无参数的方法;如果有clientAction,那么这个serverAction指向的方法应该是一个带MapEvent 参数的方法。

ArcGIS Server Java ADF 案例教程 18

五 自定义Tool实现点击地图居中【案例】

在这一小节中,我们通过自定义一个Tool来激活这样的地图功能:每次点击地图,都以这个点击点为中心使地图居中。

如下,我们新建了这样一个类CenterAtToolAction:

import com.esri.adf.web.data.WebContext;
import com.esri.adf.web.data.geometry.WebPoint;
import com.esri.adf.web.faces.event.MapEvent;
import com.esri.adf.web.faces.event.MapToolAction;

public class CenterAtToolAction implements MapToolAction
{
public void execute(MapEvent event) throws Exception
{
WebContext webContext = event.getWebContext();
WebPoint webPoint = (WebPoint)event.getWebGeometry().toMapGeometry(webContext.getWebMap());
webContext.getWebMap().centerAt(webPoint, 0.5);
webContext.refresh();
}
}


这个类的关键是实现了MapToolAction的execute方法,当一个Tool被激活,并在地图上进行了操作以后,一个请求就会被发送到服务器,对应的MapToolAction的execute方法就会被执行。

这里,我们通过MapEvent的获得WebContext和WebGeometry 对象。比如我们在浏览器中点击了一下,那么WebGeometry就是一个点对象,当然,这个点坐标是基于屏幕坐标的,我们需要调用它的toMapGeometry方法转化成地理坐标。

当这些准备工作完成以后,我们就可以使用WebMap的centerAt方法使地图居中,随后刷新WebContext输出响应到浏览器。

如图 11是点击居中前后的地图。

ArcGIS Server Java ADF 案例教程 17

四 Tool

Tool是另外一种按钮,虽然它在外观上和Command可能没什么区别,但是当Tool被点击以后,地图状态就进行了改变。比如我当前默认对地图的操作是漫游,通过点击一个Tool,接下来在地图上的点击可能就变成了放大操作——与地图交互,这就是Tool的作用。

以下代码定义了一个“使地图处于漫游状态”的Tool:

Tool定义时两个最重要的属性就是clientAction和serverAction。clientAction定义的是在浏览器端执行的操作,比如这里的“EsriMapContinuousPan”代表浏览器端执行的是连续漫游操作——当然,比如你需要在地图里画个多边形之类的操作也可以使用其它的定义,可用的操作大概如表 1所列:
EsriEditingLine 编辑直线 EsriMapLine 画直线
EsriEditingPoint 编辑点 EsriMapMouseWheel 滚轮滚动
EsriEditingPolygon 编辑多边形 EsriMapOval 画椭圆
EsriEditingPolyline 编辑多线 EsriMapPan 漫游
EsriMapCircle 画圆 EsriMapPoint 画点
EsriMapContinuousPan 连续漫游 EsriMapPolygon 画多边形
EsriMapImage 添加图片 EsriMapPolyline 画多线
EsriMapKeyNavigation 键盘导航 EsriMapRectangle 画矩形
表 1 浏览器端支持的操作列表

当用户在浏览器执行了操作以后,ADF会把这个操作相关的信息发送到服务器,然后Tool的serverAction属性中定义的这个类就起作用了。比如上面的这个Tool,服务器接到请求以后会通过PanToolAction的定义对请求过来的参数进行处理(比如向右漫游100个像素),经过一系列计算以后它更新服务器端的WebMap等对象的状态,所有工作完成以后再调用WebContext的refresh方法进行刷新,随后输出响应到浏览器。

serverAction指向的类主要需要实现MapToolAction 接口,在下面的案例中,我们会实现一个自己的ToolAction类。

2009年4月24日星期五

PostgreSQL+PostGIS的使用 5

五、 PostGIS示例

下面我们通过一个简单的Flex应用示例来看一下PostGIS的用法:

假想现在发生了恐怖袭击,导致在一些城市有污染物出现,现在我们要根据污染物和当地风力、风向情况,计算污染扩散范围,针对这些区域及时进行警报和疏散。

首先我们希望获得所有发生污染的城市的当前风速、风向等信息,在我们的PostGIS数据库中有一个空间表保存着这些信息,我们构造这样的SQL语句进行查询:
select *,ST_AsGeoJson(shape) from sde.wind

这里会获取所有风相关的信息,并且附加了以JSON格式返回的几何信息,这有助于我们在Flex中进行解析。如下图是关于风的查询结果:

下面我们希望PostGIS帮助我们实现一些空间分析。我们以污染发生的城市为起点,当地风向为主方向,构造一个30度开角的范围;这个范围将是污染扩散的主要方向,扩散的范围主要和风的强度有关;在构造这个区域以后,为了保险起见,我们在对其进行一定范围的缓冲,最后得到每个污染源可能扩散的范围。我们构造的SQL语句如下:
select *,ST_AsGeoJson( ST_Buffer( ST_PolygonFromText( 'POLYGON((' ||ST_X(shape)||' '||ST_Y(shape)||',' ||ST_X(shape)+velocity*cos((direction+15)*PI()/180)/20||' '||ST_Y(shape)+velocity*sin((direction+15)*PI()/180)/20||',' ||ST_X(shape)+velocity*cos((direction-15)*PI()/180)/20||' '||ST_Y(shape)+velocity*sin((direction-15)*PI()/180)/20||',' ||ST_X(shape)||' '||ST_Y(shape)||'))' ) , velocity/50 ) ) from sde.wind

下面是PostGIS进行运算后返回的结果:

在这里,Flex应用与服务器的交互通过BlazeDS进行,下面是本示例在服务器端的Java代码:

package wuyf;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashMap;

public class Wind
{
private Connection conn = null;

public Connection getConn()
{
if (conn==null)
{
try
{
Class.forName("org.postgresql.Driver");
String url = "jdbc:postgresql://localhost:5432/sde" ;
conn = DriverManager.getConnection(url, "sde" , "pwd" );
conn.setAutoCommit(false);
}
catch(Exception e)
{
System.err.print(e);
}
}

return conn;
}

public ArrayList
> getWinds()
{
ArrayList
> result = new ArrayList>();

if ( this.getConn()==null )
return result;

try
{
String sql = "select *,ST_AsGeoJson(shape) from sde.wind";

Statement st = this.getConn().createStatement();
st.setFetchSize(0);
ResultSet rs = st.executeQuery(sql);
while (rs.next())
{
HashMap
map = new HashMap();
map.put("shape", rs.getString("ST_AsGeoJson"));
map.put("velocity", rs.getString("velocity"));
map.put("direction", rs.getString("direction"));
result.add(map);
}
rs.close();
st.close();
}
catch(Exception e)
{
System.err.print(e);
}

return result;
}


public ArrayList
> getEffectZones()
{
ArrayList
> result = new ArrayList>();

if ( this.getConn()==null )
return result;

try
{
String sql = "select *,ST_AsGeoJson(";
sql+= "ST_Buffer(";
sql+= "ST_PolygonFromText(";
sql+= "'POLYGON(('";
sql+= "||ST_X(shape)||' '||ST_Y(shape)||','";
sql+= "||ST_X(shape)+velocity*cos((direction+15)*PI()/180)/20||' '||ST_Y(shape)+velocity*sin((direction+15)*PI()/180)/20||','";
sql+= "||ST_X(shape)+velocity*cos((direction-15)*PI()/180)/20||' '||ST_Y(shape)+velocity*sin((direction-15)*PI()/180)/20||','";
sql+= "||ST_X(shape)||' '||ST_Y(shape)||'))'";
sql+= ")";
sql+= ", velocity/50";
sql+= ")";
sql+= ") ";
sql+="from sde.wind";

Statement st = this.getConn().createStatement();
st.setFetchSize(0);
ResultSet rs = st.executeQuery(sql);
while (rs.next())
{
HashMap
map = new HashMap();
map.put("shape", rs.getString("ST_AsGeoJson"));
map.put("velocity", rs.getString("velocity"));
map.put("direction", rs.getString("direction"));
result.add(map);
}
rs.close();
st.close();
}
catch(Exception e)
{
System.err.print(e);
}

return result;
}

}

PostgreSQL+PostGIS的使用 4

2. PostGIS扩展函数

管理函数:

删除一个空间表(包括geometry_columns中的记录) DropGeometryTable([], )
更新空间表的空间参考 UpdateGeometrySRID([], , , )
更新空间表的统计信息 update_geometry_stats([, ])

参考语义:
Geos:GEOS库
Jts:JTS库
Proj:PROJ4库 postgis_version()

postgis_lib_version()
postgis_lib_build_date()
postgis_script_build_date()
postgis_scripts_installed()
postgis_scripts_released()
postgis_geos_version()
postgis_jts_version()
postgis_proj_version()
postgis_uses_stats()
postgis_full_version()

几何操作符:

A范围=B范围 A = B
A范围覆盖B范围或A范围在B范围左侧 A &<> B
A范围在B范围左侧 A <<>> B
A范围覆盖B范围或A范围在B范围下方 A &<| B A范围覆盖B范围或A范围在B范围上方 A |&> B
A范围在B范围下方 A <<| B A范围在B范围上方 A |>> B
A=B A ~= B
A范围被B范围包含 A @ B
A范围包含B范围 A ~ B
A范围覆盖B范围 A && B

几何量测函数:

量测面积 ST_Area(geometry)
根据经纬度点计算在地球曲面上的距离,单位米,地球半径取值6370986米 ST_distance_sphere(point, point)
类似上,使用指定的地球椭球参数 ST_distance_spheroid(point, point, spheroid)
量测2D对象长度 ST_length2d(geometry)
量测3D对象长度 ST_length3d(geometry)
根据经纬度对象计算在地球曲面上的长度 ST_length_spheroid(geometry,spheroid)
ST_length3d_spheroid(geometry,spheroid)
量测两个对象间距离 ST_distance(geometry, geometry)
量测两条线之间的最大距离 ST_max_distance(linestring,linestring)
量测2D对象的周长 ST_perimeter(geometry)
ST_perimeter2d(geometry)
量测3D对象的周长 ST_perimeter3d(geometry)
量测两点构成的方位角,单位弧度 ST_azimuth(geometry, geometry)

几何对象输出:
参考语义:
NDR:Little Endian
XDR:big-endian
HEXEWKB:Canonical
SVG:SVG 格式
GML:GML 格式
KML:KML 格式
GeoJson:GeoJson 格式

ST_AsBinary(geometry,{'NDR'|'XDR'})
ST_AsEWKT(geometry)
ST_AsEWKB(geometry, {'NDR'|'XDR'})
ST_AsHEXEWKB(geometry, {'NDR'|'XDR'})
ST_AsSVG(geometry, [rel], [precision])
ST_AsGML([version], geometry, [precision])
ST_AsKML([version], geometry, [precision])
ST_AsGeoJson([version], geometry, [precision], [options])

几何对象创建:

参考语义:
Dump:转储 ST_GeomFromEWKT(text)

ST_GeomFromEWKB(bytea)
ST_MakePoint(, , [], [])
ST_MakePointM(, , )
ST_MakeBox2D(, )
ST_MakeBox3D(, )
ST_MakeLine(geometry set)
ST_MakeLine(geometry, geometry)
ST_LineFromMultiPoint(multipoint)
ST_MakePolygon(linestring, [linestring[]])
ST_BuildArea(geometry)
ST_Polygonize(geometry set)
ST_Collect(geometry set)
ST_Collect(geometry, geometry)
ST_Dump(geometry)
ST_DumpRings(geometry)

几何对象编辑:

给几何对象添加一个边界,会使查询速度加快 ST_AddBBOX(geometry)
删除几何对象的边界 ST_DropBBOX(geometry)
添加、删除、设置点 ST_AddPoint(linestring, point, [])
ST_RemovePoint(linestring, offset)
ST_SetPoint(linestring, N, point)
几何对象类型转换 ST_Force_collection(geometry)
ST_Force_2d(geometry)
ST_Force_3dz(geometry), ST_Force_3d(geometry),
ST_Force_3dm(geometry)
ST_Force_4d(geometry)
ST_Multi(geometry)
将几何对象转化到指定空间参考 ST_Transform(geometry,integer)
对3D几何对象作仿射变化 ST_Affine(geometry, float8, float8, float8, float8, float8, float8, float8, float8, float8, float8, float8, float8)
对2D几何对象作仿射变化 ST_Affine(geometry, float8, float8, float8, float8, float8, float8)
对几何对象作偏移 ST_Translate(geometry, float8, float8, float8)
对几何对象作缩放 ST_Scale(geometry, float8, float8, float8)
对3D几何对象作旋转 ST_RotateZ(geometry, float8)
ST_RotateX(geometry, float8)
ST_RotateY(geometry, float8)
对2D对象作偏移和缩放 ST_TransScale(geometry, float8, float8, float8, float8)
反转 ST_Reverse(geometry)
转化到右手定则 ST_ForceRHR(geometry)
参考IsSimple函数
使用Douglas-Peuker算法 ST_Simplify(geometry, tolerance)
ST_SimplifyPreserveTopology(geometry, tolerance)
讲几何对象顶点捕捉到网格 ST_SnapToGrid(geometry, originX, originY, sizeX, sizeY)
ST_SnapToGrid(geometry, sizeX, sizeY), ST_SnapToGrid(geometry, size)
第二个参数为点,指定原点坐标 ST_SnapToGrid(geometry, geometry, sizeX, sizeY, sizeZ, sizeM)
分段 ST_Segmentize(geometry, maxlength)
合并为线 ST_LineMerge(geometry)

线性参考:

根据location(0-1)获得该位置的点 ST_line_interpolate_point(linestring, location)
获取一段线 ST_line_substring(linestring, start, end)
根据点获取location(0-1) ST_line_locate_point(LineString, Point)
根据量测值获得几何对象 ST_locate_along_measure(geometry, float8)
根据量测值区间获得几何对象集合 ST_locate_between_measures(geometry, float8, float8)

杂项功能函数:
几何对象的摘要 ST_Summary(geometry)
几何对象的边界 ST_box2d(geometry)
ST_box3d(geometry)
多个几何对象的边界 ST_extent(geometry set)
0=2d, 1=3dm, 2=3dz, 3=4d ST_zmflag(geometry)
是否包含Bounding Box ST_HasBBOX(geometry)
几何对象的维数:2、3、4 ST_ndims(geometry)
子对象的个数 ST_nrings(geometry)
ST_npoints(geometry)
对象是否验证成功 ST_isvalid(geometry)
扩大几何对象 ST_expand(geometry, float)
计算一个空间表的边界范围 ST_estimated_extent([schema], table, geocolumn)
获得空间参考 ST_find_srid(, , )
几何对象使用的内存大小,单位byte ST_mem_size(geometry)
点是否在圆上 ST_point_inside_circle(,,,)
获取边界的X、Y、Z ST_XMin(box3d)
ST_YMin(box3d)
ST_ZMin(box3d)
ST_XMax(box3d)
ST_YMax(box3d)
ST_ZMax(box3d)
构造一个几何对象的数组 ST_Accum(geometry set)

长事务支持:
启用/关闭长事务支持,重复调用无副作用 EnableLongTransactions()
DisableLongTransactions()
检查对行的update和delete操作是否已授权 CheckAuth([],
, )
锁定行 LockRow([],
, , , [])
解锁行 UnlockRows()
在当前事务中添加授权ID AddAuth()

其它还有SQL-MM和ArcSDE样式的函数支持,可以参考http://postgis.refractions.net/documentation/manual-1.3/ch06.html#id2750611,这里就不详细列了。

PostgreSQL+PostGIS的使用 3

四、 PostGIS中的常用函数

以下内容包括比较多的尖括号,发布到blogger的时候会显示不正常,内容太多我也无暇一个个手动改代码,因此如有问题就去参考PostGIS官方文档。

首先需要说明一下,这里许多函数是以ST_[X]yyy形式命名的,事实上很多函数也可以通过xyyy的形式访问,在PostGIS的函数库中我们可以看到这两种函数定义完全一样。

1. OGC标准函数

管理函数:
添加几何字段 AddGeometryColumn(, , , , , )
删除几何字段 DropGeometryColumn(, , )
检查数据库几何字段并在geometry_columns中归档 Probe_Geometry_Columns()
给几何对象设置空间参考(在通过一个范围做空间查询时常用) ST_SetSRID(geometry, integer)

几何对象关系函数
获取两个几何对象间的距离 ST_Distance(geometry, geometry)
如果两个几何对象间距离在给定值范围内,则返回TRUE ST_DWithin(geometry, geometry, float)
判断两个几何对象是否相等
(比如LINESTRING(0 0, 2 2)和LINESTRING(0 0, 1 1, 2 2)是相同的几何对象) ST_Equals(geometry, geometry)
判断两个几何对象是否分离 ST_Disjoint(geometry, geometry)
判断两个几何对象是否相交 ST_Intersects(geometry, geometry)
判断两个几何对象的边缘是否接触 ST_Touches(geometry, geometry)
判断两个几何对象是否互相穿过 ST_Crosses(geometry, geometry)
判断A是否被B包含 ST_Within(geometry A, geometry B)
判断两个几何对象是否是重叠 ST_Overlaps(geometry, geometry)
判断A是否包含B ST_Contains(geometry A, geometry B)
判断A是否覆盖 B ST_Covers(geometry A, geometry B)
判断A是否被B所覆盖 ST_CoveredBy(geometry A, geometry B)
通过DE-9IM 矩阵判断两个几何对象的关系是否成立 ST_Relate(geometry, geometry, intersectionPatternMatrix)
获得两个几何对象的关系(DE-9IM矩阵) ST_Relate(geometry, geometry)

几何对象处理函数:
获取几何对象的中心 ST_Centroid(geometry)
面积量测 ST_Area(geometry)
长度量测 ST_Length(geometry)
返回曲面上的一个点 ST_PointOnSurface(geometry)
获取边界 ST_Boundary(geometry)
获取缓冲后的几何对象 ST_Buffer(geometry, double, [integer])
获取多几何对象的外接对象 ST_ConvexHull(geometry)
获取两个几何对象相交的部分 ST_Intersection(geometry, geometry)
将经度小于0的值加360使所有经度值在0-360间 ST_Shift_Longitude(geometry)
获取两个几何对象不相交的部分(A、B可互换) ST_SymDifference(geometry A, geometry B)
从A去除和B相交的部分后返回 ST_Difference(geometry A, geometry B)
返回两个几何对象的合并结果 ST_Union(geometry, geometry)
返回一系列几何对象的合并结果 ST_Union(geometry set)
用较少的内存和较长的时间完成合并操作,结果和ST_Union相同 ST_MemUnion(geometry set)

几何对象存取函数:
获取几何对象的WKT描述 ST_AsText(geometry)
获取几何对象的WKB描述 ST_AsBinary(geometry)
获取几何对象的空间参考ID ST_SRID(geometry)
获取几何对象的维数 ST_Dimension(geometry)
获取几何对象的边界范围 ST_Envelope(geometry)
判断几何对象是否为空 ST_IsEmpty(geometry)
判断几何对象是否不包含特殊点(比如自相交) ST_IsSimple(geometry)
判断几何对象是否闭合 ST_IsClosed(geometry)
判断曲线是否闭合并且不包含特殊点 ST_IsRing(geometry)
获取多几何对象中的对象个数 ST_NumGeometries(geometry)
获取多几何对象中第N个对象 ST_GeometryN(geometry,int)
获取几何对象中的点个数 ST_NumPoints(geometry)
获取几何对象的第N个点 ST_PointN(geometry,integer)
获取多边形的外边缘 ST_ExteriorRing(geometry)
获取多边形内边界个数 ST_NumInteriorRings(geometry)
同上 ST_NumInteriorRing(geometry)
获取多边形的第N个内边界 ST_InteriorRingN(geometry,integer)
获取线的终点 ST_EndPoint(geometry)
获取线的起始点 ST_StartPoint(geometry)
获取几何对象的类型 GeometryType(geometry)
类似上,但是不检查M值,即POINTM对象会被判断为point ST_GeometryType(geometry)
获取点的X坐标 ST_X(geometry)
获取点的Y坐标 ST_Y(geometry)
获取点的Z坐标 ST_Z(geometry)
获取点的M值 ST_M(geometry)

几何对象构造函数
参考语义:
Text:WKT
WKB:WKB
Geom:Geometry
M:Multi
Bd:BuildArea
Coll:Collection ST_GeomFromText(text,[])

ST_PointFromText(text,[])
ST_LineFromText(text,[])
ST_LinestringFromText(text,[])
ST_PolyFromText(text,[])
ST_PolygonFromText(text,[])
ST_MPointFromText(text,[])
ST_MLineFromText(text,[])
ST_MPolyFromText(text,[])
ST_GeomCollFromText(text,[])
ST_GeomFromWKB(bytea,[])
ST_GeometryFromWKB(bytea,[])
ST_PointFromWKB(bytea,[])
ST_LineFromWKB(bytea,[])
ST_LinestringFromWKB(bytea,[])
ST_PolyFromWKB(bytea,[])
ST_PolygonFromWKB(bytea,[])
ST_MPointFromWKB(bytea,[])
ST_MLineFromWKB(bytea,[])
ST_MPolyFromWKB(bytea,[])
ST_GeomCollFromWKB(bytea,[])
ST_BdPolyFromText(text WKT, integer SRID)
ST_BdMPolyFromText(text WKT, integer SRID)

PostgreSQL+PostGIS的使用 2

三、 PostGIS中空间信息处理的实现

1. spatial_ref_sys表

在基于PostGIS模板创建的数据库的public模式下,有一个spatial_ref_sys表,它存放的是OGC规范的空间参考。我们取我们最熟悉的4326参考看一下:

它的srid存放的就是空间参考的Well-Known ID,对这个空间参考的定义主要包括两个字段,srtext存放的是以字符串描述的空间参考,proj4text存放的则是以字符串描述的PROJ.4 投影定义(PostGIS使用PROJ.4实现投影)。

4326空间参考的srtext内容:
GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],TOWGS84[0,0,0,0,0,0,0],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.01745329251994328,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]]

4326空间参考的proj4text内容:
+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs

2. geometry_columns表

geometry_columns表存放了当前数据库中所有几何字段的信息,比如我当前的库里面有两个空间表,在geometry_columns表中就可以找到这两个空间表中几何字段的定义:

其中f_table_schema字段表示的是空间表所在的模式,f_table_name字段表示的是空间表的表名,f_geometry_column字段表示的是该空间表中几何字段的名称,srid字段表示的是该空间表的空间参考。

3. 在PostGIS中创建一个空间表

在PostGIS中创建一个包含几何字段的空间表分为2步:第一步创建一个一般表,第二步给这个表添加几何字段。

以下先在test模式下创建一个名为cities的一般表:
create table test.cities (id int4, name varchar(20))

再给cities添加一个名为shape的几何字段(二维点):
select AddGeometryColumn('test', 'cities', 'shape', 4326, 'POINT', 2)

4. PostGIS对几何信息的检查

PostGIS可以检查几何信息的正确性,这主要是通过IsValid函数实现的。
以下语句分辨检查了2个几何对象的正确性,显然,(0, 0)点和(1,1)点可以构成一条线,但是(0, 0)点和(0, 0)点则不能构成,这个语句执行以后的得出的结果是TRUE,FALSE。

select IsValid('LINESTRING(0 0, 1 1)'), IsValid('LINESTRING(0 0,0 0)')
默认PostGIS并不会使用IsValid函数检查用户插入的新数据,因为这会消耗较多的CPU资源(特别是复杂的几何对象)。当你需要使用这个功能的时候,你可以使用以下语句为表新建一个约束:
ALTER TABLE cities
ADD CONSTRAINT geometry_valid
CHECK (IsValid(shape))

这时当我们往这个表试图插入一个错误的空间对象的时候,会得到一个错误:
INSERT INTO test.cities ( shape, name )
VALUES ( GeomFromText('LINESTRING(0 0,0 0)', 4326), '北京');

ERROR: new row for relation "cities" violates check constraint "geometry_valid"
SQL 状态: 23514

5. PostGIS中的空间索引

数据库对多维数据的存取有两种索引方案,R-Tree和GiST(Generalized Search Tree),在PostgreSQL中的GiST比R-Tree的健壮性更好,因此PostGIS对空间数据的索引一般采用GiST实现。

以下的语句给sde模式中的cities表添加了一个空间索引shape_index_cities,在pgAdmin中也可以通过图形界面完成相同的功能。
CREATE INDEX shape_index_cities
ON sde.cities
USING gist
(shape);

另外要注意的是,空间索引只有在进行基于边界范围的查询时才起作用,比如“&&”操作。

PostgreSQL+PostGIS的使用 1

二、 PostGIS中的几何类型

PostGIS支持所有OGC规范的“Simple Features”类型,同时在此基础上扩展了对3DZ、3DM、4D坐标的支持。

1. OGC的WKB和WKT格式

OGC定义了两种描述几何对象的格式,分别是WKB(Well-Known Binary)和WKT(Well-Known Text)。

在SQL语句中,用以下的方式可以使用WKT格式定义几何对象:
POINT(0 0) ——点
LINESTRING(0 0,1 1,1 2) ——线
POLYGON((0 0,4 0,4 4,0 4,0 0),(1 1, 2 1, 2 2, 1 2,1 1)) ——面
MULTIPOINT(0 0,1 2) ——多点
MULTILINESTRING((0 0,1 1,1 2),(2 3,3 2,5 4)) ——多线
MULTIPOLYGON(((0 0,4 0,4 4,0 4,0 0),(1 1,2 1,2 2,1 2,1 1)), ((-1 -1,-1 -2,-2 -2,-2 -1,-1 -1))) ——多面
GEOMETRYCOLLECTION(POINT(2 3),LINESTRING((2 3,3 4))) ——几何集合

以下语句可以使用WKT格式插入一个点要素到一个表中,其中用到的GeomFromText等函数在后面会有详细介绍:
INSERT INTO table ( SHAPE, NAME )
VALUES ( GeomFromText('POINT(116.39 39.9)', 4326), '北京');

2. EWKT、EWKB和Canonical格式

EWKT和EWKB相比OGC WKT和WKB格式主要的扩展有3DZ、3DM、4D坐标和内嵌空间参考支持。

以下以EWKT语句定义了一些几何对象:
POINT(0 0 0) ——3D点
SRID=32632;POINT(0 0) ——内嵌空间参考的点
POINTM(0 0 0) ——带M值的点
POINT(0 0 0 0) ——带M值的3D点
SRID=4326;MULTIPOINTM(0 0 0,1 2 1) ——内嵌空间参考的带M值的多点

以下语句可以使用EWKT格式插入一个点要素到一个表中:
INSERT INTO table ( SHAPE, NAME )
VALUES ( GeomFromEWKT('SRID=4326;POINTM(116.39 39.9 10)'), '北京' )

Canonical格式是16进制编码的几何对象,直接用SQL语句查询出来的就是这种格式。

3. SQL-MM格式

SQL-MM格式定义了一些插值曲线,这些插值曲线和EWKT有点类似,也支持3DZ、3DM、4D坐标,但是不支持嵌入空间参考。

以下以SQL-MM语句定义了一些插值几何对象:
CIRCULARSTRING(0 0, 1 1, 1 0) ——插值圆弧
COMPOUNDCURVE(CIRCULARSTRING(0 0, 1 1, 1 0),(1 0, 0 1)) ——插值复合曲线
CURVEPOLYGON(CIRCULARSTRING(0 0, 4 0, 4 4, 0 4, 0 0),(1 1, 3 3, 3 1, 1 1)) ——曲线多边形
MULTICURVE((0 0, 5 5),CIRCULARSTRING(4 0, 4 4, 8 4)) ——多曲线
MULTISURFACE(CURVEPOLYGON(CIRCULARSTRING(0 0, 4 0, 4 4, 0 4, 0 0),(1 1, 3 3, 3 1, 1 1)),((10 10, 14 12, 11 10, 10 10),(11 11, 11.5 11, 11 11.5, 11 11))) ——多曲面

PostgreSQL+PostGIS的使用 0

一、 PostgreSQL与PostGIS的关系

PostgreSQL是世界上技术最先进的开源数据库,其前身是1977年一个源于Berkeley名为Ingres的非关系型数据库,其项目领导人为Michael Stonebraker教授。1982年该教授商业化了Ingres;1985年,Michael Stonebraker教授回到Berkeley,开始对新的数据库设计进行研究,并于次年在美国防务高级研究项目局(DARPA)、陆军研究办公室(ARO)、国家科学基金(NSF)以及ESL, Inc等机构的赞助下启动了Postgres(Post-Ingres)项目。

Postgres在1987年形成第一个Demo,1989年发布第一个版本,直到1993年的4.2版本,由于外部用户过多,做技术支持和维护源代码的时间影响到了对数据库的研究,因此Berkeley中止了该项目。在此期间,Postgres项目就已经被使用在了一些GIS系统中。

Postgres项目并未就此消亡,在1994年两个Berkeley的研究生向Postgres中加入了SQL语言解释器,将之改名为Postgre95并发布到了互联网上。经过一些黑客的修改,1996年Postgres95再次更名为PostgreSQL,并采用BSD许可证发布了第一个开源版本。经过多年发展,PostgreSQL已经发展成为一个技术非常先进的开源数据库,其支持特性之多性能之强可与诸多高级商业数据库比肩。

这里提供了一个世界上主流数据库的特性比较,有兴趣的可以从中管窥PostgreSQL在数据库领域中的成就。
http://en.wikipedia.org/wiki/Comparison_of_relational_database_management_systems

PostGIS则是PostgreSQL的一个扩展,目的是使PostgreSQL支持空间数据的存储和使用,其本质类似于ArcSDE和Oracle Spatial Extension。PostGIS是采用GPL许可发布的,完整地实现了OGC的《Simple Features Specification for SQL》规范,并于2006年获得OGC认证。在此基础上,PostGIS还对规范进行了一些扩展,在后面的特性中我们可以慢慢了解到。

2009年4月22日星期三

SDE与PostGIS的集成

PostgreSQL:http://www.postgresql.org/
PostGIS:http://postgis.refractions.net/
PostGIS Manual:http://postgis.refractions.net/documentation/manual-1.3/

安装PostgreSQL
这里没有使用SDE自带的PostgreSQL,而是独立的PostgreSQL 8.3.6.

安装PostGIS扩展
可以通过PostgreSQL的Application Stack Builder安装,或者下载独立安装包。这里下载了PostGIS 1.3.5安装包进行安装。

这个版本在安装PostGIS的时候会提示错误,原因是PostgreSQL锁住了这个文件,因此先停掉PostgreSQL,但安装完成后必然造成初始化PostGIS数据库失败,可以在PostgreSQL启动后再运行一次PostGIS安装程序,再见到这个错误的时候忽略。

这时通过pgAdmin连接到数据库,可以看到PostGIS创建了相关的数据库。

安装ArcSDE for PostgreSQL
在安装ArcSDE for PostgreSQL之前,首先新建一个基于PostGIS模板的数据库用于ArcSDE。注意,使用sde角色需要有管理员权限并可创建数据库对象。

然后安装ArcSDE相关程序文件,接着运行Post Installation。

我们希望SDE在保存数据的时候使用PG_GEOMETRY格式保存空间数据,而不是默认的ST_GEOMETRY,因为使用PG_GEOMETRY格式可以使用PostGIS的功能。这里需要自定义“dbtune.sde”文件 。将这个文件备份后,修改以下这段:
GEOMETRY_STORAGE "ST_GEOMETRY"
为:
GEOMETRY_STORAGE "PG_GEOMETRY"
然后,选择这个修改过的“dbtune.sde”文件。

以下与其它ArcSDE Post Installation类似。

测试SDE图层使用PostGIS功能
在ArcCatalog中新建到ArcSDE的连接:

然后使用ArcCatalog导入若干Feature Class到SDE,这里使用了Sample的World中的cities等图层进行测试:

打开pgAdmin,刷新sde数据库中的sde模式,查询cities表:

下面测试一下不通过ArcSDE直接对这个表进行PostGIS空间查询。打开SQL命令窗口,测试使用如下SQL:
SELECT * FROM cities WHERE ST_Distance(shape, GeomFromText('POINT(116.39 39.9)', 4326))<10

以上空间查询SQL代表查找在4326空间参考下,与点(116.39, 39.9)【北京附近】距离10以内的所有城市,查询结果如下,总共29条记录:

2009年4月21日星期二

ArcGIS Server Java ADF 案例教程 16

三 WebContext的refresh方法

提示:本小节内容是ADF内部实现机制,对ADF的使用没有影响,如没有兴趣可以跳过。

当调用WebContext的refresh方法的时候, ADF会同步WebContext中所有相关属性。比如当我改变了WebMap的视图范围,然后再调用WebContext的refresh方法,那么ADF会通知WebContext中的WebOverview等对象去同步相关信息,比如同步改变视图范围等。以上过程ADF在后台是通过Observer机制实现的。当ADF调用WebContext的refresh方法时,ADF会通知所有注册到WebContext的WebContextObserver 对象,调用其update方法。实现WebContextObserver这个接口的大概有如下这些类:AWSTocFunctionality, ExtentHistory, MapToolsTask, PrintTask, RoutingTask, SearchAttributesTask, WebMap, WebOverview, WebScaleBar。

以上对WebContext的刷新都是在服务器端。但是,当一个Command被点击以后,服务器端的更新又是怎么被输出到浏览器端的呢?下面就让我们从浏览器端开始,详细地了解一下Command的工作原理。

以下是上一节的案例中用a:command标签在JSF页面中定义了一个Command,在下面我们看一下这段标签在浏览器中相应的输出:

以下是上面的标签在浏览器中的输出:
var zoomIn = new EsriMapServerAction("zoomIn", "");
zoomIn.clientPostBack = true;
zoomIn.defaultImage = "/AgsDemo/images/tasks/maptools/zoomin.png";
zoomIn.hoverImage = "/AgsDemo/images/tasks/maptools/zoominU.png";
zoomIn.selectedImage = "/AgsDemo/images/tasks/maptools/zoominD.png";
zoomIn.isDisabled = false;
zoomIn.showLoading = true;
toolbar.addToolItem(zoomIn);


可见,一个Command对应输出的是EsriMapServerAction这个JavaScript对象,这个对象在“/WebContent/js/esri_map.js”中定义。打开EsriMapServerAction的定义你会发现,当点击这个Command的时候,ADF会提交当前页面;当然,由于我们设置了clientPostBack属性为TRUE,因此交互通过AJAX方式进行,其回调函数为EsriControls的processPostBack函数——EsriControls在“/WebContent/js/esri_core.js”中定义,它包含了所有的ADF客户端组件。

提示:以上这段这里说的很简单,其实包含很多的内容,有兴趣的话可以在这几个JavaScript脚本中仔细研究相关对象和调用关系。

ADF在服务器端会有一个PostBackPhaseListener 去监听所有的AJAX请求,有兴趣的话你可以解开“/WebContent/WEB-INF/lib/arcgis_webcontrols.jar”这个库文件,在其中的“META-INF”目录下有一个“faces-config.xml”中定义了这个PhaseListener。

AJAX请求的参数中会有一个“doPostBack”值,当一个AJAX请求发到服务器的时候,PostBackPhaseListener会监听到这个请求并找到“doPostBack”这个值从而执行后续的操作。当这些操作完成以后,PostBackPhaseListener通过AJAXUtil 的writeResponse方法输出AJAX响应到请求的浏览器。

2009年4月20日星期一

ArcGIS Server Java ADF 案例教程 15

二 【案例】自定义Command使地图等比放大

在这一小节,我们通过自定义Command定制一个按钮,这个按钮每点击一次,地图会放大1倍。通过这个简单案例,让我们来熟悉一下Command的用法。

实现这个功能的主要环节是实现自己的监听器类,以下是这个类的定义:
import com.esri.adf.web.data.WebContext;
import com.esri.adf.web.util.WebUtil;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.ActionEvent;
import javax.faces.event.ActionListener;

public class ZoomInListener implements ActionListener
{
public static double ZOOM_FACTOR = 0.5;

public void processAction(ActionEvent event) throws AbortProcessingException
{
WebContext webContext = WebUtil.getWebContext(event.getComponent());
webContext.getWebMap().getCurrentExtent().expand(ZOOM_FACTOR);
webContext.refresh();
}
}

当然,在JSF页面中的用法和上面类似,也很简单:

这个ZoomInListener监听器类主要需要实现了ActionListener的processAction方法,当用户点击这个id为“zoomIn”的Command的时候,这个监听器会被触发,然后其processAction方法就会被执行。

在这个processAction方法中,我们首先获得WebContext对象,通过WebContext我们就可以获得WebMap对象,进而对它的currentExtent属性进行操作(地图范围缩小1倍),再刷新WebContext对象。这个过程很简单,中间有两个环节这里稍作解释。

一个环节是如何获得WebContext对象。processAction是JSF的ActionListener接口的一个方法,这个方法的参数是ActionEvent ,因此它的属性与ADF基本没有关系,所以,获得WebContext就需要用到ADF的WebUtil工具类,ESRI的工程师为了可以在各种环境下获得WebContext这个ADF中非常重要的环境,制造了WebUtil这个工具类及其getWebContext方法,通过这个方法我们可以很方便地获得WebContext对象。

另外一个环节就是WebContext对象的refresh方法,我们在对服务器端的WebMap对象进行一些操作以后,调用了WebContext的refresh方法以后,浏览器中的地图就会刷新成新的视图,其相关的TOC、Overview等组件也会跟着刷新,这很奇妙吧。

ArcGIS Server Java ADF 案例教程 14

Command、Tool和Button

这一章我们将讨论的主要对象是Toolbar的孩子们和亲戚,涉及到两个孩子:Command和Tool,还有一个亲戚:Button。它们是基于ADF对WebGIS功能进行扩展的第一步,它可以让你在地图工具条中加入若干自定义的按钮来执行特定的功能。

在第三章中我们把Toolbar列为ADF常用组件之一,但是它和Map、TOC、Overview等其它组件是有区别的,我们打开“context-attributes.xml”配置文件可以发现,Map、TOC、Overview等组件都有相应的JavaBean定义,但是Toolbar没有——因为它不需要在服务器端绑定一个“WebToolbar”对象来记录Toolbar的状态信息。

下面让我们分别来看看这些组件的用法吧。

一 Command
Command在Toolbar中表现为一个按钮,当用户点击这个按钮以后,ADF将会立即执行一些操作。点击Command相当于直接给ADF一个命令,它并不改变当前地图的状态,比如当前地图正在进行漫游,点击Command以后再对地图进行操作时它还是漫游状态。

最典型的Command比如“地图全图”功能,当用户点击以后,当前地图会缩放到全图显示。下面的代码定义了这样一个Command。

从这段代码我们可以看到,Command的最关键的用法在于定义其子元素“f:actionListener”,它的“type”属性指向一个实现了某些功能的监听器类(注意,并不是对象)。这里的“ZoomFullExtentListener”是ADF自带的监听器;当然,这个类也可以是任何实现了ActionListener 接口的类,我们在后面的案例会实现我们自己的监听器类。

下面我们再来看两个稍微有点不同的Command定义。这两个Command的功能是提供两个按钮“后退”、“前进”,当点击“后退”时地图会返回到上一视图范围;当点击“前进”时地图会前进到后一视图范围。下面的代码定义了这样两个Command。

这里并没有使用监听器,而是使用了Command的action属性。这个属性将绑定一个服务器端对象的方法,当点击这个Command的时候,ADF会去调用这个服务器端方法。“后退”、“前进”的功能主要使用了ADF中的ExtentHistory 对象,它可以维护地图的视图范围历史记录。下面它是在“context-attributes.xml”中的定义:

当我们点击“后退”按钮的时候,ADF会调用这个服务器端“history”对象的doPrevious方法,因此地图会切换到上一视图;点击“前进”也是类似。另外,这两个Command的disabled属性分别绑定到“history”对象的canUndo和canRedo属性,这也很好理解,当地图已经是最新的视图的时候,用户当然应该不可以点击“前进”这个按钮,此时“history”的canRedo属性值为TRUE,因此“前进”这个按钮会呈现不可用的状态。

稍微总结一下,上面主要介绍了Command的两种定义用法,一种定义监听器类作为Command的子元素;另外一种绑定服务器端方法到Command的action属性。对于自定义Command来说,一般使用监听器会比较容易一些。

2009年4月17日星期五

基于PHP的Flex Socket安全策略设置

Flex打开Socket连接前会按几个步骤检查安全策略配置,如不成功将报安全沙箱错误。最先检查的就是目标主机的843端口是否提供了安全策略配置,因此在843端口监听Socket请求并输出安全策略配置是最直接的解决Flex Socket连接沙箱错误的办法。

如下PHP代码在服务器端被执行以后将会完成上述工作:

在Window中,在控制台中执行“telnet localhost 843”命令后如果可以输出安全策略文件,表明设置成功。

B/S架构中的数据推送技术

B/S架构中服务器向浏览器推送数据在很多场合都有需求,比如实时的监控报警、实时的调度,等等。凡是对实时性要求越高的场景,越是需要服务器及时、准确地向浏览器推送数据。这里我们就讨论一下在B/S架构下,可以实现从服务器向浏览器推送数据的几种技术及其相应的特点。

基于HTTP协议

1. HTTP协议的特点

纯的HTTP协议在本质上是无状态、无连接的,它基于请求/响应的工作模式,使得浏览器在每次发生请求的时候和服务器建立连接,当接收到响应的时候断开连接;在这种情况下,要让服务器主动向浏览器发送数据是不可能的。

技术总是为需求服务的,在很多浏览器需要及时获取服务器数据更新的需要下,技术人员们变通地发明了一些基于HTTP协议的“伪”长连接技术,实现了服务器数据向浏览器的“准”推送。

2. 定时刷新

定时刷新是最粗糙的实现快速感知服务器数据变化的方法,原理就是不停地刷新页面从而显示最新的服务器端数据。定时刷新从技术上大体有两种实现方法:一是通过HTML的META标签设置页面刷新间隔,比如以下的标签会在浏览器中每隔10秒刷新当前页面:
<meta equiv="refresh" content="10" />

这种方式会看到页面有明显的刷新,用户体验会比较差;与此相对应的另外一种方式就是AJAX,在JavaScript脚本中添加一个定时器,每隔一定时间向服务器发送一个AJAX请求,得到响应以后去更新页面的内容。以下JavaScript代码会每隔10秒发送一个AJAX请求去更新当前页面的部分内容:
setInterval(function(){sendAJAXRequest()},10000);

如图 1是定时刷新的模型图:
图 1 定时刷新的模型图

定时刷新这种模式相当于一个下级每隔一段时间打电话给上级,请示当前指示。当然,如果在这个时间段内发生了一些情况,他们之间是没办法沟通的,所以信息并不是“实时”的;另外,刷新间隔到底设置多少合适也是个问题,服务器的负担也比较大。

3. 轮询
轮询也是通过AJAX进行的,它与AJAX定时刷新的区别仅在于接收到AJAX响应以后的工作。定时刷新完全是浏览器主动的,在浏览器和服务器之间存在一定的断开间隔;而轮询这种方式会在AJAX响应中再次发送AJAX请求,也就是说当响应结束,浏览器和服务器之间一旦断开连接的时候,浏览器会再次发送请求建立连接。

如图 2是轮询的模型图:
图 2 轮询的模型图

轮询这种模式相当于一个下级不停给上级打电话请示,上级指示下达以后挂了电话,下级可能去执行指示或者不执行,但是随即下级将再次给上级打电话继续请示。这种模式可以保证数据更新比较实时,但是服务器负担也是一个问题。

顺便提一下,轮询这种模式一般会在浏览器的XMLHttpRequest的readyState为4(响应数据传输结束)的时候调用回调函数,此时连接已经关闭;但是在Firefox中支持Streaming AJAX模式,也就是XMLHttpRequest的readyState为3(响应数据仍在传输)的时候调用回调函数,此时连接尚未关闭。

4. 基于iframe的流模式

iframe可以在当前页面中嵌入一个文档页面,如果把这个页面设为隐藏,并将其src属性设置为一个特殊的请求,数据将会源源不断地从服务器发送到浏览器。

比如以下的JSP页面,它将每隔10秒更新一个数输出到浏览器,不出意外它将会永远执行下去,浏览器和服务器之间的连接也永远不会关闭:
<% int i = 1; try { while (true) { out.print("

"+(i++)+"

");
out.flush();

try {
Thread.sleep(10000);
} catch (InterruptedException e) {}
}
} catch (Exception e) {}
%>


但是这样做有个很显然的不足,由于响应始终没有结束,因此浏览器里面的加载进度条会始终显示加载没有完成。当然,这也并不是没有办法解决,比如http://www.zeitoun.net/articles/comet_and_php/start就提供了IE、Opera、WebKit核心(Chrome/Safari)、Gecko核心(Firefox)等浏览器的解决方案。

如图 3是基于iframe流模式的模型图:
图 3 基于iframe流模式的模型图

基于iframe的流模式相当于一个下级给上级打电话,上级不停发出指示,下级一边接收指示、一边执行。这种模式有着比较好的实时性,比如Gmail就是采用这种模式。

5. 开源框架Pushlets
Pushlets是一个实现了AJAX轮询和iframe流模式的开源框架(Java+JavaScript),对此有兴趣的可以参考:http://www.pushlets.com/,Pushlets采用LGPL许可。

基于消息

1. 基于消息的架构

使用消息可以实现松散耦合的分布式数据通讯。通过消息从服务器向浏览器推送数据一般需要一个消息中间件(Message Oriented Middleware,MOM),服务器将数据推送给消息中间件,消息中间件再将数据以消息的方式推送给浏览器。基于消息的架构有个特别大的优点,那就是不但可以实现服务器向单浏览器、服务器向多浏览器推送数据;还支持浏览器到浏览器之间的数据推送。这将会在预警、调度等场合有非常大的用武之地。

图 4和图 5分别是基于订阅/发布模式和点到点模式的消息传递示意。在B/S架构中,服务器可以发布消息,所有订阅该主题的浏览器都会接受到该消息,这就实现了从服务器向多浏览器的数据推送;浏览器或者服务器也可以向特定的对象发送消息,消息将在一个消息队列中被发送,对方浏览器就可以收到该消息,这就实现了服务器向某特定浏览器或者浏览器之间的数据推送。

2. Java消息服务(JMS)


JMS是一组公开的Java API,它定义了与消息相关的接口和语义,目前JMS已经成为J2EE中的重要组成部分。
图 6 JMS工作原理

如图 6是JMS工作原理。当有客户端连接到JMS服务器的时候,JMS的连接工厂会根据连接类型来创建一个虚拟连接,这个连接会具体负责消息的传递;在这个连接建立完成后,会产生一个会话,会话中保存了消息生产者或消息消费者的信息;消息的消费者会对感兴趣的消息目的地(队列或主题)建立一个监听,消息的生产者则负责把消息发送到这个目的地上。

另外JMS还有一些值得一提的特点,比如支持事务性会话、可以设置消息的持久性、设置消息的优先级、允许消息过期、可以构建长期订阅等。这些特性在各种企业级应用环境下都有可能提高应用的功能或性能。

实现JMS的商业中间件有IBM MQSeries、BEA WebLogic JMS等;开源中间件有OpenJMS、Apache ActiveMQ等。

3. ActiveMQ

ActiveMQ是Apache基金会的著名开源项目,目前Release版本5.2完整支持JMS1.1和J2EE 1.4规范。ActiveMQ采用Apache 2.0许可发布。ActiveMQ的优势在于其支持集群部署、支持多种应用层协议和诸多客户端开发语言等特点。

ActiveMQ主要支持以下协议:
 OpenWire
 REST
 Stomp
 WS Notification
 XMPP
 AMQP

下面这里将使用ActiveMQ和Stomp协议来演示各种方式的数据的推送。

4. Stomp协议

Stomp是一种简单、实现容易的协议,因此支持非常广泛,这里采用Stomp协议的主要原因也是因为其支持的客户端开发语言最多,在各种环境下都有用武之地。这些开发语言主要包括:
 ActionScript 3
 C
 C++
 .Net
 Delphi
 Perl
 PHP
 Python
 Ruby

下面主要用到了ActionScript和.Net语言。

5. 发布/订阅模式的实现

发布/订阅模式适用于广播性质的数据推送。比如在实时监控系统中,当我们的指令中心需要向所有监控目标发送信息的时候,这种模式就比较适合。为了简单起见,这里的服务器端和浏览器端都使用了ActionScript实现。

在浏览器端,我们需要订阅服务器主题。比如所有的接收指令的目标都需要监听“Alarm”频道,那么在浏览器中的代码应该如下:
private function sub():void
{
var ch:ConnectHeaders = new ConnectHeaders();
stomp.connect("localhost", 61613, ch);
stomp.subscribe( "/topic/Alarm" );
}
private function onStompMessage(event:MessageEvent):void
{
var byteArray:ByteArray = event.message.body;
var str:String = byteArray.toString();
}


图 7 发布/订阅模式的数据推送

6. 点对点模式的实现

点对点模式适合类似调度的功能场景。比如在监控系统中,当某个指令需要下达给具体某个目标的时候,就需要点对点模式的数据推送。

实现点对点模式的数据推送需要知道数据发送的目的地,以下代码演示了如何从指令中心“党中央”发送指令到“毒蛇”的过程:
private function sendMsg():void
{
var destination:String = "/queue/毒蛇";
stomp.sendTextMessage(destination, “注意隐蔽”);
}


当然,“毒蛇”需要监听所有发送给他的消息:
private function login():void
{
var ch:ConnectHeaders = new ConnectHeaders();
stomp.connect("localhost", 61613, ch);
stomp.subscribe( "/queue/毒蛇" );
}

图 8 点对点模式的数据推送

7. 实时监控Demo

这里通过消息模式实现了一个实时监控的演示。服务器端是使用.Net Stomp API写的控制台程序,Demo测试使用1000个监控目标,数据发送间隔0.5秒,数据推送到“realmonitor”频道。浏览器端使用Flex,监听“realmonitor”频道。

在服务器端,这个Demo中设置每100个目标信息拼装成一条消息,也就是说每0.5秒会发送10条消息。如果每条消息的信息(坐标和一些属性信息)大约50字节的话,每条消息大概5KB;每秒发送两次,总共大概100KB数据量会被推送。

图 9 Flex中实时监控效果

如图 9是在Flex中实现的实时监控效果。