ArcGIS Maritime Server 开发教程(七)Maritime Server 正确的开发模式

版权声明:欢迎分享地理价值,但请注明出处,http://blog.csdn.net/liyuanxiang1984 https://blog.csdn.net/liyuanxiang1984/article/details/80099285

ArcGIS Maritime Server 开发教程(七)Maritime Server 正确的开发模式

本章导读:前面几个章节已经非常细节的分析和测试了 Maritime Server 的相关功能了,在面对重重困难的时候,很多 GIS 开发者甚至在想, Maritime Server 除了发布海图服务之外难道就在开发定制上一筹莫展吗?传统的 ArcGIS Server 功能非常强大,ArcGIS JavaScript API 接口也非常丰富,一旦转入到关于 Maritime 的时候,就显得捉襟见肘了。所以,笔者也在想,ArcGIS Maritime 团队到底是怎样利用其设计的产品完成一个个大型的项目的?可以肯定的是,解决问题的方法不在于 Maritime 产品本身,而在于其开发模式。 BY 李远祥

ArcGIS Maritime Server 你想要的帮助文档

对于开发人员来说,什么是最想要的?那肯定是开发帮助和样例代码,这样就能够以最短的时间最快的方式切入到具体的功能开发中。一般来说,程序员的英文水平都比较高,英文的 SDK 都看得很溜,敲起代码来来比用企鹅聊天还要快。其实大批量出现的都是那几个计算机专业词汇,又或者代码时使用的大量的是程序语言中出现的关键词汇 。总之,习惯就好了。

没有中文帮助,有英文的也不错,就像 ArcGIS Server ,各种产品说明,部署说明,功能说明等都有在中国官网上;开发 API 是全英的,SDK 和 Sample Code 也是全英的,但胜在这一块是程序员的优势,读起来也不算吃力。是在不行的话,国内某知名搜索引擎还是可以检索出前人一堆的代码,还带上中文注释(幸运的话还会在注释出看到程序员诗兴大发编上去的一首打油诗 ^_^ )

但是,切换到 ArcGIS Maritime 就没有那么幸运了。产品的部署说明还是有的,各种的升级、迁移说明,都能找到。如下图所示:
ArcGIS Maritime 官方帮助

这就是官方帮助的网址,特别留意一下,是带”zh-cn”的。也就是说,目前还没能像 ArcGIS Server 那样有官方的全中文帮助说明。

尽管如此,笔者还是建议开发人员去看看,这不是了胜于无,而是在专业领域上是在没有办法了,总不能靠猜去完成整个开发过程,必须是实打实的了解产品,了解原理,这样才能从有限的帮助中“感悟”出一些必要的信息出来。也许在项目结束的时候,你还庆幸有这么一丁点的英文资料。

但在 https://developers.arcgis.com/ 上根本上检索不到有任何 Maritime 字样的 API 说明,关于这方面的接口说明,可以肯定是没有的了。那么服务的 REST API 又怎样?前面的文章已经解释过了,如果原始的 REST 页面去访问,提供的输入参数部分,几乎没有一个能够正常返回结果的,所以,无法通过抓取请求/返回的方式去“领悟”并自己构建和解释请求。在这方面,是既没有专门的 Maritime JavaScript API ,也没有最原始的 REST API 说明,完全是处于“你猜猜看”的状态。

通过搬梯子到墙外看看,也没能在 G 姓的搜索引擎中找到一些可用的开发资源。所以,建议开发人员先死了通过搜索去找到帮助的那条心。在这条路中唯一可以依赖的搜索引擎是国内的百度,目前关于 ArcGIS Maritime 开发教程只有中文。没错!真的只有中文,并且跳转的地方是 CSDN 李老师的博客。这个还真只能是苦中作乐。

所以,大篇幅的谈论上述话题,为的只是说明一个问题,你想要的帮助是不存在你最想要的开发帮助这一次非常幸运,就只有中文!

ArcGIS Maritime Server 开发模式猜想

从第一章《ArcGIS Maritime Server 开发教程(一)了解ArcGIS Maritime Server》笔者就已经提到 ArcGIS Maritime Server 的系统架构,架构图上已经写得非常的情况,ArcGIS Maritime Server 实际上是在 ArcGIS Server 上的一个 SOE 扩展,这跟其他的服务器扩展如 Image Server,GeoEvent 等完全不一样。通过 SOE 扩展了海图渲染引擎,对外的服务协议还是全部走 ArcGIS Server 的标准协议,对外接口是保持一致的。尽管实际上的使用有各种问题,其具体的原因笔者在前几章也有非常详细的说明。

按照 ArcGIS Server 的 SOE 扩展模式,它属于在 Server 后台做的功能,原则上是不会作用到前端接口上的。也就是说,从技术路线的选定上,ArcGIS Maritime Server 压根就不会存在像 ImageServer 那样单独的开发接口。

从基本的服务调用、查找、地图交互操作方面,都使用通用的 ArcGIS JavaScript API 去实现,但大部分功能还是不能与传统服务对齐。所以,直接使用 Maritime Server 提供的服务(Maritime Service)功能非常弱。

所以笔者大胆的猜想,ArcGIS Maritime Server 最大的贡献就是能够直接将海图数据 S-57 数据发布成标准的 GIS 服务,并且能够按照 S-52 标准显示出来。这部分的工作如果使用传统的 ETL 以及配图的方式,工作量巨大无比,而且在数据更新以及维护上非常吃力 。至于具体的应用,笔者大胆的猜想,应该不是 ArcGIS Maritime Server 所做的事情,而是返回到传统的 ArcGIS Server 模式,借助成熟的产品和全面的 API ,定制开发应该是轻松的事情。而 ArcGIS Maritime Server 在整个系统的作用,已经就是解决海图数据的支撑以及海图显示的问题。

之前过于依赖 Maritime Service ,那是因为 ArcGIS 在这一块上发力,解决了海图应用中工作量最大也是难度最大的数据发布和海图渲染的问题,让很多人都忽略了最根本的 ArcGIS Server 服务。再大胆一点猜想,在没有 ArcGIS Maritime Server 的时候,海图服务的发布都是通过传统配图方式出来的,发布的数据都已经是从 S-57 转换过来的 Geodatabase 格式的数据。对于这个数据,GIS 开发人员肯定很容易掌握,只要是它发布的服务,ArcGIS JavaScript API 的各种接口都能顺利使用。试想一下,将这两种方式结合起来会是什么样子?

用 Maritime Server 去发布海图服务,海图服务用于前台显示,释放整个电子海图配图的工作;将海图数据转换到 Geodatabase 中,再发布一个没有配图的 MapService ,这个服务不做叠加,只用来在后台做属性查询、空间查询以及地理分析,这种模式可以有效的解决海图显示和开发的所有问题。

使用全新的 Maritime 开发模式

在解决一个问题的时候,往往还会引入另一个新的问题。所以,前面提及到的这种开发模式,会引入另一个工作,就是将 .000 数据转为 GDB 格式。到底怎样转才是正确的,怎样处理才是最高效的,那就值得商榷了。

笔者在前面章节《ArcGIS Maritime Server 开发教程(六)Maritime Service 开发技巧》中介绍过,只要安装了 ArcGIS S-57 Viewer 之后,ArcMap 就能直接识别 S-57 数据,并将其按照 GIS 要素类方式显示。能够通过右键方式将整个 000 数据导出为 GDB 格式,如下图所示:

尽管这个方式是将数据转出来了,但海图数据是分幅的数据,也许每个分幅对应的物标种类不一样,分幅之间接边怎么处理,导出的多个 GDB 如何合并等一系列问题都需要解决。

当然,处理这些工作笔者也可以通过自己编写一些 Python 工具进行扩展。从 000 导出 GDB 然后再去合并所有分幅的 GDB 数据。但这种方法依然很欠妥,每个海图分幅的范围和种类不一样,编写代码要则要考虑比较周全。

在现成的工具下,最有效的管理 S-57 数据的方式是使用 ArcGIS Martime Charting 桌面扩展,通过 NIS 库进行入库管理(非海图生产,只是做数据管理)。安装好 ArcGIS Desktop 软件之后,再单独安装 ArcGIS Martime Charting 扩展模块。其工作方式是以 ArcMap 桌面软件的工具和工具条来实现。

NIS 库是 ArcGIS Maritime Charting 在 Geodatabase 下创建的库结构,主要作用是海图数据存储在 GIS 数据库中,能够使用 GIS 的工具和方法使用这些非 GIS 数据。NIS 的构建方式不在这个章节详细讨论,因为有更好的方式可以实现快速将系列海图数据转存到 NIS 库并制作成地图文档。

通过 GitHub ,检索到 Esri 官方放出的一些海图的工具及代码,有了几个工具一切就简单多了。且看具体的几个工具,这是具体的链接,截图如下
ArcGIS GitHub 上的工具

下载下来后最重要的是第一个工具,S57 To GIS 。先来看工具的构成,分别由两个配置好的 MXD 文件,一个 ArcGIS 工具箱,一个 Python 工具脚本以及一个 .zip 压缩包组成,压缩包内其实是预先生成好的空的 NIS 数据库。如下图所示:
S57 To GIS 工具

从 README文件中可以看到前面有关的重要说明,一是功能,而是工具所需的运行环境。

# S57 To GIS

S57 to GIS is a python script that allows you to quickly and easily load one, or many, ENC cells into a
geodatabase that is symbolized with S-52.

Tool requirements:
* ArcMap 10.4.1 with ArcGIS for Maritime: Charting 10.4.1
* Ensure that the Maritime Charting extension is enabled
* The S57 To GIS.tbx and s57_to_gis Python file must be in the same directory

这个工具是快速的将一个或多个 ENC 数据转为 GDB 格式,并以 S-52 的方式进行符号化。所需的环境是 ArcMap10.4.1 以上,及对应版本的 ArcGIS Maritime Charting 扩展模块。

用法很简单,先将 zip 文件解压,得到一个空的 NIS 库,然后根据数据的情况是否使用带 SCAMIN 的模板,两个 MXD 选定的其中一个。新建一个地图文档(不要使用模板的 mxd 文件来操作,因为最终工具需要将此 mxd 进行覆盖,如果打开了该模板,将无法将信息写入),在 Catalog 中指向 S57 To GIS 工具箱,双击打开该工具,如下图所示
S57 To GIS 工具操作说明

  • 第一个参数是指向 S57 文件所在的目录,该目录中可以含有1个或多个 S57 文件。
  • 第二个参数指向的是解压出来的 NIS 数据库。如果自建有基于 ArcSDE 的 NIS 库也可以。
  • 第三个参数指向是地图模板文件,就是工具目录下的两个 mxd 文件中的其中一个。

为了方便管理,笔者建议是将解压出来的 GDB 文件与地图模板放在同一个目录下,操作完成后打开 ArcMap 将文档的路径改为相对路径。这样方便数据的拷贝和迁移。

执行完之后可以看到工具的执行提示,分别是将 ENC 数据导入到 NIS 库中,然后利用制图表达将其符号化,根据制图模板,将数据源重新设置,并保存了 mxd 文档。
操作结果

ArcGIS 桌面软件去渲染 NIS 库,使用的并非是传统的符号系统,而是使用制图表达技术,因为 S52 的渲染方式无法通过传统符号系统技术去实现。

完成后打开 Mxd 文件,可以看到地图和数据已经关联,并且以 S52 方式进行显示。

纯桌面的显示效果要比 Maritime Server 输出的效果要差一些,但我们需要的不是桌面显示,而是需要这样的一个 mxd 文件来发布传统的 ArcGIS Server 地图服务。

上面的截图是使用了带 SCAMIN 的模板制作,适合做桌面显示。因为它由多个图层组构成,每个图层组设置好了最大显示比例和最小显示比例,再桌面缩放和浏览有一定的优势。而作为一个查询的不显示的地图服务,还是建议使用 NO_SCAMIN 模板,它只有一个图层组,地图服务中比较容易使用索引指向图层。下图是 NO_SCAMIN 显示的效果。

NO_SCAMIN 模板

笔者是推荐使用 NO_SCAMIN 模板来发布用于查询的地图服务。如果用来做切片底图,那么地图服务发布使用 SCAMIN 模板。

接下来就是将 NO_SCAMIN 地图文档发布为地图服务,这个过程就不再多说了。至于这里面包含的图层,显示与不显示都不是重点,因为这个服务是用作查询和空间分析使用的。通过上述的方式,笔者发布一个名为 queryMap 的 MapService,这样就回到熟悉的开发路上了。从这个服务中可以看到加入了 NIS 库中的所有物标对象,但它的命名方式跟前面所说的 S-57 Viewer 呈现的名称不一样,需要做一些适当的对照。不过基本上是大同小异的,使用起来问题也不大。

熟悉的 ArcGIS JavaScript API 开发

使用上述的这种开发模式,立马就可以回归到熟悉的 ArcGIS JavaScript API 开发中去了,通过这种方式,克服了前面所说的种种困难。
前面章节中提及到的空间查找以及图形查找,都可以迎刃而解了,毕竟每一个图层都可以使用传统的开发接口。

根据《ArcGIS Maritime Server 开发教程(五)Maritime Service 系列问题应对》里面提及到的一些问题,例如 不支持Query查找,identify 操作不支持不规则图形等,在新的模式下这些问题都能够迎刃而解。以之前章节的 identify 图形查找为例,现在对其进行有效的改进,使用 QueryTask 做图形查找,其关键代码如下:

加载基础地图部分及海图服务,不需要将 NIS 库发布的服务加载显示。

//加载基础服务WGS 84
var basemap = new DynamicLayer("http://localhost:6080/arcgis/rest/services/SampleWorldCities/MapServer");
map.addLayer(basemap);
//加载海图服务
var enc84 = new DynamicLayer("http://localhost:6080/arcgis/rest/services/SampleWorldCities/MapServer/exts/MaritimeChartService/MapServer");
map.addLayer(enc84);

在绘图完毕之后,调用 QueryByGeometry 函数进行几何图形查找。QueryTask 调用的服务则为使用 NIS 发布的地图服务,索引号为 5 的图层对应的是水深点。

//点击地图,通过屏幕上的点进行查询
function QueryByGeometry(geom) {
    var queryParams, queryTask;
    //查找后台不显示的服务,以水深点作为查找
    queryTask = new QueryTask("http://localhost:6080/arcgis/rest/services/queryMap/MapServer/5");
    queryParams = new Query();
    queryParams.geometry = geom;
    queryParams.returnGeometry = true;
    queryTask.execute(queryParams, getResult);
}

function getResult(results) {
    //获取并显示查询结果
    var resultCount = results.features.length;
    for (var i = 0; i < resultCount; i++) {
        var feat = results.features[i];
        setGrapic(feat);
    }
}

代码片段改动了原来查询的方式,直接使用 QueryTask 来代替原来的 IdentifyTask,实现了精确的对物标要素进行图形查找。这样就不需要像之前那样需要获取物标的属性进行判断,而是在一开始 Query 参数的时候进行了指定。页面代码如下

<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  <meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no" />
  <title>使用传统服务方式空间查询海图要素(水深点为例)</title>
  <link rel="stylesheet" href="http://localhost/arcgis_js_api/3.19/esri/css/esri.css">
  <style>
    html,
    body,
    #mapDiv {
      width: 100%;
      height: 100%;
      margin: 0;
      padding: 0;
    }
  </style>
  <script src="http://localhost/arcgis_js_api/3.19/init.js"></script>
  <script>
    var map, tb;
    var drawGrapic;
    require(["esri/map",
      "esri/layers/ArcGISDynamicMapServiceLayer",
      "esri/geometry/Extent",
      "esri/symbols/SimpleMarkerSymbol",
      "esri/symbols/SimpleLineSymbol",
      "esri/symbols/SimpleFillSymbol",
      "esri/tasks/QueryTask",
      "esri/tasks/query",
      "dojo/Deferred",
      "esri/Color", "esri/graphic",
      "esri/toolbars/draw",
      "esri/symbols/PictureFillSymbol", "esri/symbols/CartographicLineSymbol",
      "dojo/on", "dojo/dom",
      "dojo/domReady!"],
      function (Map, DynamicLayer, Extent,
        SimpleMarkerSymbol, SimpleLineSymbol, SimpleFillSymbol,
        QueryTask, Query, Deferred,
        Color, Graphic, Draw,
        PictureFillSymbol, CartographicLineSymbol,
        on, dom) {
        //初始化显示范围
        var initexten = new Extent({ "xmin": 113.42, "ymin": 22.15, "xmax": 113.58, "ymax": 22.26, "spatialReference": { "wkid": 4326 } });
        map = new Map("mapDiv", { extent: initexten });
        //加载基础服务WGS 84
        var basemap = new DynamicLayer("http://localhost:6080/arcgis/rest/services/SampleWorldCities/MapServer");
        map.addLayer(basemap);
        //ENC WGS 84
        var enc84 = new DynamicLayer("http://localhost:6080/arcgis/rest/services/SampleWorldCities/MapServer/exts/MaritimeChartService/MapServer");
        map.addLayer(enc84);
        //注册点击查询事件
        map.on("load", initToolbar);
        //简单点符号
        var markerSymbol = new SimpleMarkerSymbol();
        markerSymbol.setColor(new Color("#00FFFF"));
        markerSymbol.setStyle(SimpleMarkerSymbol.STYLE_CIRCLE);
        markerSymbol.setSize(12);
        //制图线符号
        var lineSymbol = new CartographicLineSymbol(
          CartographicLineSymbol.STYLE_SOLID,
          new Color([255, 0, 0]), 10,
          CartographicLineSymbol.CAP_ROUND,
          CartographicLineSymbol.JOIN_MITER, 5
        );
        //图片填充
        var fillSymbol = new PictureFillSymbol(
          "/mangrove.png",
          new SimpleLineSymbol(
            SimpleLineSymbol.STYLE_SOLID,
            new Color('#000'),
            1
          ),
          42,
          42
        );

        //点击地图,通过屏幕上的点进行查询
        function QueryByGeometry(geom) {
          var queryParams, queryTask;
          //查找后台不显示的服务,以水深点作为查找
          queryTask = new QueryTask("http://localhost:6080/arcgis/rest/services/queryMap/MapServer/5");
          queryParams = new Query();
          queryParams.geometry = geom;
          queryParams.returnGeometry = true;
          queryTask.execute(queryParams, getResult);
        }
        function getResult(results) {
          //获取并显示查询结果
          var resultCount = results.features.length;
          for (var i = 0; i < resultCount; i++) {
            var feat = results.features[i];
            setGrapic(feat);
          }         
        }
        //渲染查找结果
        function setGrapic(gra) {
          //定义点线面符号
          var pointSym = new SimpleMarkerSymbol();
          var lineSym = new SimpleLineSymbol();
          var polySym = new SimpleFillSymbol();
          var color = new Color([255, 0, 0, 0.5]);
          var outColor = new Color([0, 255, 0, 0.5]);
          //点符号设置
          pointSym.setColor(color);
          pointSym.setSize(12);
          pointSym.outline.setColor(outColor);
          //线符号设置
          lineSym.setColor(color);
          lineSym.setWidth(3);
          //面符号设置
          polySym.setColor(color);
          polySym.outline.setColor(outColor);
          //对点线面图形设置符号
          if (gra.geometry.type == "point") {
            gra.symbol = pointSym;
          } else if (gra.geometry.type == "polyline") {
            gra.symbol = lineSym;
          } else if (gra.geometry.type == "polygon") {
            gra.symbol = polySym;
          }
          map.graphics.add(gra);
          //跳转到地图位置
          jumptoMap(gra); 
        }
        //跳转到要素所在的位置
        function jumptoMap(gra) {
          if (gra.geometry.type == "point") {
            map.centerAndZoom(gra.geometry);
          } else {
            map.setLevel(6);
            map.setExtent(gra.geometry.getExtent());
          }
        }

        //跳转到要素所在的位置
        function jumptoMap(gra) {
          if (gra.geometry.type == "point") {
            map.centerAndZoom(gra.geometry);
          } else {
            map.setLevel(6);
            map.setExtent(gra.geometry.getExtent());
          }
        }

        function initToolbar() {
          tb = new Draw(map);
          tb.on("draw-end", addGraphic);
          on(dom.byId("info"), "click", function (evt) {
            if (evt.target.id === "info") {
              return;
            }
            var tool = evt.target.id.toLowerCase();
            map.disableMapNavigation();
            tb.activate(tool);
          });
        }

        function addGraphic(evt) {
          tb.deactivate();
          map.enableMapNavigation();
          var symbol;
          if (evt.geometry.type === "point" || evt.geometry.type === "multipoint") {
            symbol = markerSymbol;
          } else if (evt.geometry.type === "line" || evt.geometry.type === "polyline") {
            symbol = lineSymbol;
          }
          else {
            symbol = fillSymbol;
          }
          var drawGrapic2 = new Graphic(evt.geometry, symbol);
          map.graphics.add(drawGrapic2);
          QueryByGeometry(evt.geometry);
        }
      });
  </script>
</head>
<body>
  <div id="info">
    <button id="Point">Point</button>
    <button id="Multipoint">Multipoint</button>
    <button id="Line">Line</button>
    <button id="Polyline">Polyline</button>
    <button id="FreehandPolyline">Freehand Polyline</button>
    <button id="Triangle">Triangle</button>
    <button id="Extent">Rectangle</button>
    <button id="Circle">Circle</button>
    <button id="Ellipse">Ellipse</button>
    <button id="Polygon">Polygon</button>
    <button id="FreehandPolygon">Freehand Polygon</button>
  </div>
  <div id="mapDiv"></div>
</body>
</html>

再来看测试结果,通过输入不同的图形进行查询水深点,全部都能满足图形查询要求。如下图所示

使用图形查询水深点结果

总结

通过仔细分析和大胆猜想,基于 SOE 架构的 ArcGIS Maritime Server 是不会带有任何特定的开发 API 的,所以打算直接使用所谓的 Maritime API 去进行开发,这条路从一开始就走不通。ArcGIS Maritime Server 更多是从服务器后端去定制的功能,暴露在前端的则是传统的地图服务接口,使用传统的 ArcGIS JavaScript API 去调用和定制才是唯一可行的道路。通过从 GitHub 上下载下来的 S57 To GDB 工具,很好的封装了 ArcGIS Maritime Charting 扩展的入库工具,给海图数据转到 GDB 中提供了极大的便利,同时也解决了新模式下海图应用开发的所有问题。

更多的GIS主流和非主流技术,可以持续关注CSDN的GIS制图乐园,以及微信公众号【GIS制图乐园】。BY 李远祥

阅读更多

扫码向博主提问

李远祥

博客专家

非学,无以致疑;非问,无以广识
去开通我的Chat快问
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页