zoukankan      html  css  js  c++  java
  • 转ArcGIS Server Java ADF 案例教程

     

    http://jerryyuan2.spaces.live.com/blog/cns!2B4FFBE981AC2760!201.entry

    第一章 有关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桌面程序,演示意义大于应用意义。下面我们首先来讨论一下如何使用SOAP SDK来访问ArcGIS Server原生的Web Service;然后再谈谈怎么自定义Web Service,以便其它业务系统调用或出于其它目的的SOAP访问。

    一 通过SOAP SDK访问ArcGIS Web Service

    这 里我们通过一个简单的功能来演示如何使用SOAP SDK来访问ArcGIS Server,调用一个Map Service的功能。不管你是在Java桌面、Applet、ADF还是自定义的servlet中,如果你需要直接使用SOAP和ArcGIS交互,那 么下面的这段内容对你可能会有帮助。

    这个简单的功能是“计算当前范围内的要素个数“,这个功能是如此的简单以至于我觉得看一下代码就可以明白了:

    MapServerBindingStub mapserver = new MapServerBindingStub(url);

    SpatialFilter filter = new SpatialFilter();

    filter.setFilterGeometry(mapDescription.getMapArea().getExtent());

    filter.setSpatialRel(EsriSpatialRelEnum.esriSpatialRelIntersects);

    filter.setGeometryFieldName("SHAPE");

    filter.setSubFields("*");

    filter.setSearchOrder(EsriSearchOrder.esriSearchOrderSpatial);

    filter.setWhereClause("");

    filter.setSpatialReferenceFieldName("");

    filter.setSpatialRelDescription("");

    int count = mapserver.queryFeatureCount(“Layers”, 0, (QueryFilter) filter);

    JOptionPane.showMessageDialog(this, "Feature count = " + count);

    上 面涉及到的各个ArcGIS的功能类都在com.esri.arcgisws包下,从命名上就可以看出这是专门为Web Service准备的。代码里用到的url是地图服务的地址,比如“http://host:8399/arcgis/services/name /MapServer“。在整个环节中你可能会对MapServerBindingStub的queryFeatureCount方法有一点兴趣,其实你 打开ArcGIS帮助里的SOAP SDK,找到Map Services下面的QueryFeatureCount方法就可以看到,QueryFeatureCount需要3个参数,分别对应 DataFrame名称、图层序号和QueryFilter,正与上面的代码相符。换句话说,com.esri.arcgisws包里的功能类就是 SOAP SDK的Java实现。

    可是,不管是ADF还是REST SDK都提供了比SOAP SDK好用多的调用ArcGIS Server功能的方法,所以想来你会和我一样,对上面的这些内容兴趣寥寥——我觉得ESRI对SOAP SDK也是兴趣寥寥,以至于在SOAP SDK的文档里几乎都没有Java的示例代码。

    好了,让我们抛开ArcGIS提供的Web Service,来看看怎么自定义Web Service吧。我个人觉得自定义的Web Service是更加有用的,而且,功能也可以做得更强大。

     两种方式自定义Web Service

    在IDE里创建一个Web Service工程是很容易的,只要找到【File】-【New】-【Web Service】菜单就可以了,点击这个菜单将会弹出下面的对话框:

    image

    这 里的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需要的功能是预先定义好的,其中有比如上下游追溯、连通性分析等等,这里只挑连通性分析这个小功能进行说明。

    首先,我们通过给定的wsdl新建了一个Web Service工程,然后找到实现类,在这个类中已经根据wsdl自动生成了对应各个功能的方法,这里就连通性分析功能进行讲解,对应的方法是connectivity,下面是这个方法的实现:

    public class TestGISAnalysisSoapBindingImpl implements TestGISAnalysisPortType

    {

    private GeoNetworkAnalysis geoNetAnalysis;

    public TestGISAnalysisSoapBindingImpl()

    {

    geoNetAnalysis = new GeoNetworkAnalysis();

    }

    public java.lang.String connectivity(String sourceDeviceType

    , String sourceDeviceID

    , String targetDeviceType

    , String targetDeviceID,

    String type) throws java.rmi.RemoteException

    {

    String result = "";

    try

    {

    result = geoNetAnalysis.findPath(Integer.parseInt(sourceDeviceType)

    , Integer.parseInt(sourceDeviceID)

    , Integer.parseInt(targetDeviceType)

    , Integer.parseInt(targetDeviceID)

    , type);

    }

    catch (Exception ex)

    {

    ex.printStackTrace();

    }

    return result;

    }

    }

    怎么这么简单?当然,因为这里对功能做了封装,真正的分析功能在geoNetAnalysis这个对象中,在这个对象中进行的都是直接对AO的操作,我们通过一些代码片段来看一下这个功能的实现:

    public class GeoNetworkAnalysis

    {

    public GeoNetworkAnalysis()

    {

    try {

    EngineInitializer.initializeEngine();

    new AoInitialize().initialize(esriLicenseProductCode.esriLicenseProductCodeArcInfo);

    ResourceBundle bundle = ResourceBundle.getBundle("geometrynetwork");

    traceFlowSolver = new TraceFlowSolver();

    INetSolver netSolver = (INetSolver) traceFlowSolver;

    geometricNetwork = openNetwork(bundle.getString("workspacePath"),

    bundle.getString("datasetName"),bundle.getString("networkName"));

    pNetElements = new INetElementsProxy(geometricNetwork.getNetwork());

    netSolver.setSourceNetworkByRef(geometricNetwork.getNetwork());

    ...

    }

    catch (Exception e)

    {

    e.printStackTrace();

    }

    }

    /**

    * 连通性分析

    *

    @param sourceLyrID

    * 起点的图层ID

    @param sourceFeatID

    * 起点的要素ID

    @param targetLyrID

    * 终点的图层ID

    @param targetFeatID

    * 终点的要素ID

    @param type

    @return

    */

    public String findPath(int sourceLyrID

    int sourceFeatID

    int targetLyrID

    int targetFeatID

    , String type) throws Exception

    {

    this.putTraceOrigins(getOriginPoints(sourceLyrID

    ,sourceFeatID

    ,targetLyrID

    ,targetFeatID));

    IEnumNetEID[] junctionEIDS = new IEnumNetEID[1];

    IEnumNetEID[] edgeEIDs = new IEnumNetEID[1];

    Object[][] costArray = new Object[1][1];

    traceFlowSolver.findPath(esriFlowMethod.esriFMConnected,

    esriShortestPathObjFn.esriSPObjFnMinSum

    , junctionEIDS

    , edgeEIDs

    , 1

    , costArray);

    ...

    Set<IFeature> junctions = this.getSolverResultFeature(junctionEIDS[0]);

    Set<IFeature> edges = this.getSolverResultFeature(edgeEIDs[0]);

    return FeatureUtil.networkResultToJSON(junctions, edges);

    }

    }

    注意,上面只是代码片段,整个功能的实现代码太长了,和我们要了解的Web Service其实又没有什么关系,所以,就看个大概吧。

    做完这些,在很多场合都可以调用这个自定义的Web Service了,比如上面这个功能我们是在Flex中调用的:

    <mx:Script>

    <![CDATA[

    private function analysisConnectivity():void

    {

    opt = wsAnalysis.getOperation("connectivity");

    args = new Object();

    args.sourceDeviceType = g1.attributes.layerId;

    args.sourceDeviceID = g1.attributes.OBJECTID;

    args.targetDeviceType = g2.attributes.layerId;

    args.targetDeviceID = g2.attributes.OBJECTID;

    args.type = "connectivity";

    opt.arguments = args;

    opt.send();

    }

    ]]>

    </mx:Script>

    <mx:WebService id="wsAnalysis" wsdl="http://localhost:8080/WebServiceProject/wsdl/TestGISAnalysisHttpSoapEndpoint.wsdl"

    showBusyCursor="true"

    concurrency="last"

    result="onWsAnalysisResult(event)"

    fault="onWsAnalysisFault(event)"/>

    试想一下,是不是不同的业务系统之间交互也可以做了呢?

    第一章 扩展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对象的过程和特点。

    image

    图 29 ADF调用服务器端对象的过程

    如 图 29所示,当用户发生请求的时候,ADF首先会和SOM交互(图中1);SOM将根据服务器情况分配一个SO用以处理这个用户的请求,同时返回这个SO的 代理(proxy)给ADF(图中2);然后,ADF就会通过这个代理和服务器的SO进行交互(图中3,注意,这是ADF应用和GIS服务器之间的交互, 是跨进程的);当请求完成后,ADF释放相应的资源供其它用户使用(图中4)。

    跨进程的调用会消耗比较多的系统资源,响应速度较慢;当 ADF应用和GIS服务器分开部署的时候,还有相应的网络开销。因此,当你的ADF应用中有大量细粒度操作,频繁地对服务器端AO对象进行调用的时候,性 能会非常低下。Utility Objects就是为了解决这个问题而诞生的。

    Utility Objects的原理就是把上述分别属于ADF和GIS服务器两个进程中的操作合并到一起,都放到ArcGIS Server中执行,这样可以极大地减小进程间通讯的开销。在实现上Utility Objects也并不复杂,你只需要把原来直接在ADF中实现的功能,封装到一个实现了自定义接口的类中,然后在ADF中调用这个自定义的功能类就可以 了。下面让我们看一下这个大体的流程,在ArcGIS Server帮助中有这样一个例子:把计算一个FeatureLayer中所有多边形面积总和的功能封装成一个Utility Object。

    首先,我们需要定义一个接口ICalculateArea,并使用CalcAreaUtil类来实现这个接口,注意,Utility Object的接口和类都需要使用“@ArcGISExtension”注记来声明:

    @ArcGISExtension

    public interface ICalculateArea

    {

    public double calculateArea(IFeatureLayer featureLayer)

    throws IOException, AutomationException;

    }

    @ArcGISExtension

    public class CalcAreaUtil implements ICalculateArea

    {

    public double calculateArea(IFeatureLayer featureLayer)

    {

    double totalArea = 0;

    IFeatureClass featureClass = featureLayer.getFeatureClass();

    int numFeatures = featureClass.featureCount(null);

    IFeatureCursor featureCursor = featureLayer.search(nulltrue);

    IFeature feature = null;

    Polygon polygon = null;

    for (int i = 0; i < numFeatures; i++)

    {

    feature = featureCursor.nextFeature();

    polygon = (Polygon)feature.getShape();

    totalArea += polygon.getArea();

    }

    return totalArea;

    }

    }

    有 两种方法可以部署这个Utility Object,当然,作为一个懒人我比较倾向于选择相对简单、绿色的方法:将上面实现的这个类编译成jar包,再将这个jar包复制到服务器上的 %ARCGISHOME%/java/lib/ext 目录下。另外一种复杂一些,而且让我觉得有点脏兮兮的方法,有兴趣的可以到帮助文档中寻觅一下。

    好,下面我们就可以使用这个Utility Object了:

    IFeatureLayer featureLayer = mapServer.getLayer(" ", 2);

    ICalculateArea calArea = (ICalculateArea)

    com.esri.arcgis.server.ServerContext.createObject(CalcAreaUtil.class);

    double totalArea = calArea.calculateArea(featureLayer);

    在这段代码中我们看到,调用“计算一个FeatureLayer中多边形总面积”的功能全部由一个GIS服务器端的CalcAreaUtil类实现,ADF中只需要使用IServerContext来实例化这个类,再调用它相应的方法即可。

     使用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 (featureCount >= first + pageSize)

    {

    break;

    }

    if (featureCount >= first)

    {

    result.add(ServiceUtil.feature2Einfo(feature));

    }

    featureCount++;

    feature = featureCursor.nextFeature();

    }

    经过这样的处理,性能有了极大的提升,一个简单的测试可以说明情况:这里做了一个半径1000公里的空间查询,返回结果总数为30437条。

    分页起始记录

    分页记录数

    优化前(s)

    优化后(s)

    0

    500

    179.406

    5.203

    如果你有细粒度的操作,又遇到了性能问题,现在是不是很心动?

    一 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管理界面:

    image

    图 30 添加了SOE的Map Service

    是不是可以把SOE理解成一个服务的自定义Capability?似乎ESRI就是这样理解的。那下面我们就通过自定义一个新的Capability来看看SOE的用法。

    二 创建一个自定义的Server Object Extension【案例】

    首先,我们使用IDE插件来帮助我们新建这个SOE,打开菜单的【File】-【New】-【Server Object Extension】打开新建窗口,如图 31;然后在如图 32的属性页中做些自定义的编辑,ArcGIS插件会自动新建一些类和配置文件。

    image

    图 31 新建SOE窗口

    image

    图 32 编辑一些SOE的属性

    好,现在虽然我们没有添加什么实质的功能代码,但是SOE的框架已经起来了,让我们尝试部署这个SOE到ArcGIS Server中去。

    首 先,右键工程,选择【Export】,找到Java文件夹下的JAR文件选项,把这个SOE导出成jar包;然后,复制这个jar文件到 %ARCGISHOME%/java/lib/ext 目录下;最后,需要用一个工具添加这个SOE到Map Service,这个工具叫SOMManager,你可以在 %ARCGISHOME%/java/tools目录下打开控制台,然后执行以下命令:

    soemanager -a [server name] [domain name] [username] [password] [fully qualified SOE name] [SOE display name]

    比如上面在wuyf命名空间下新建了一个NMEAServerObjectExt类,因此大概的命令就是如下这个样子的:

    soemanager -a localhost localhost arcgismanager passwd wuyf.NMEAServerObjectExt NMEA

    好了,下面在添加一个新的Map Service的时候就可以看到一个新的Capability了:

    image

    图 33 自定义的NMEA Capability

  • 相关阅读:
    SL复习笔记之平稳转型——基础篇(二、控件和数据访问)
    UML之用例图和类图
    SL复习笔记之平稳转型——基础篇(四、多媒体,工具提示和右键菜单)
    SL复习笔记之平稳转型——基础篇(一)
    SL复习笔记之平稳转型——基础篇(三、SL安装检测和用“刷子”刷出背景)
    平稳转型WP系列之在Windows Phone中谈“委托”、“事件”和“接口”(一、深入理解)
    UML建模之活动图和StarUML使用
    SL复习笔记之平稳转型——基础篇(五、数据绑定)
    使用html parser
    设计模式工厂模式
  • 原文地址:https://www.cnblogs.com/zuiyirenjian/p/1622344.html
Copyright © 2011-2022 走看看