GIS图层概念及应用

导语

最近项目上遇到一个专题树,遇到打开专题的时间轴后,以前打开的图层会被关闭的bug过程是这样的,先打开增城区规划导则地块,而后再打开上面含有多个年份的历史图层,规划导则地块,这个时候增城区规划导则地块的图层会被关闭。web

通过代码定位和按行注释,发现是这段代码针对图层的关闭和显示有问题:json

// 处理默认年份的显示隐藏
 var layer = this.defaultLayer;
if (visible) {
    `layer.setVisibleLayers && layer.setVisibleLayers([layerobj.layerindex])`; 
    layer.setVisibility(true);
 } else {
// 关闭
   `layer.setVisibleLayers && layer.setVisibleLayers([]);`
    layer.setVisibility(false); 
}
复制代码

为何当前的 layer会把其余的图层关闭了呢?下面先理解几个概念。api

本文主要讲述如下几点内容:缓存

  • 什么是图层
  • 图层是如何加载的
  • 图层的应用:解决专题树的问题

什么是图层

图层是 ArcMap、ArcGlobe 和 ArcScene 等Arcgis 产品套件中地理数据集的显示机制。一个图层引用一个数据集,并指定如何利用符号和文本标注绘制该数据集。向地图添加图层时,要指定它的引用数据集并设定地图符号和标注属性。bash

包含一个地图控件的每一个应用程序是经过一系列图层组装的。显示以特定的顺序显示在地图上,列在最底部的显示在地图的最上面显示,也就是先添加的显示在下面显示(原理相似于“栈”)运维

全部的图层都是从Layer类型继承而来的,能够参考下载的API中的对象模型图。测试

Layer
  |–TiledMapServiceLayer
  |----|–ArcGISTiledMapServiceLayer
  |–DynamicLayer
  |----|–DynamicMapServiceLayer
  |----------|–ArcGISDynamicMapServiceLayer
  |----------|–ArcGISImageServiceLayer
  |----------|–GPResultImageLayer
  |–GraphicsLayer
  |----|–FeatureLayer
  |–ElementLayer
复制代码

而图层是怎么加载出来的呢,它是经过地图服务加载出来的。优化

图层的加载

什么是地图服务

地图服务是一种利用 ArcGIS 使地图可经过 web 进行访问的方法。咱们首先在 ArcMap 中制做地图,而后将地图发布到 ArcGIS Server 站点上。当地图服务发布成功后,咱们能够经过网址(xxxx/arcgis/rest/services)来查看地图服务所支持的操做,地图服务所包含的数据,以及咱们还能够经过网址来测试地图服务的功能。ui

以后在Web 应用程序、ArcGIS for Desktop、ArcGIS Online 以及其余客户端应用程序中请求该地址使用此地图服务this

下面说说常见的两种图层加载模式,实例化一个图层对象,须要传入图层的 url

切片服务

原理:切片服务是已经经过比例尺切好地图了,如一般的底图,通常是切片服务加载的,当你经过鼠标放大底图,它会根据当前的比例尺来加载已经切好的图片,加载的方式是经过export接口请求已经切好的图片。

因为切片服务已经切好了,因此没法经过相似setVisibleLayers来控制它的图层显示,只能经过setVisiblity 控制整个图层的显示。导出图片时,export会把整个当前比例的切片导出来。

一个发布出来的切片以下:

经过ArcGISTiledMapServiceLayer 新建一个切片类实例,而后加载到地图中

var layer = new ArcGISTiledMapServiceLayer(layerobj.url);
map.addLayer(layer);
复制代码

控制切片图层的显示

// 设置图层显示/隐藏
    layerVisibleRefreshByName: function(obj) {
      if (obj == null) return;
      var serviceid = obj.serviceid;
      var layername = obj.layername;
      var layervisible = obj.layervisible;
      // 若是 serviceid 不存在时,运维赋值为 label 名称
      if (serviceid == this.guid || serviceid == this.label) {
        this.setVisibility(layervisible);
      }
    }
复制代码

固然,若是切片服务在发布时,勾选了可使用动态服务加载的,那么切片服务也能够经过ArcGISDynamicMapServiceLayer 来加载。

动态服务

原理:

一个动态服务的信息以下:

经过ArcGISDynamicMapServiceLayer 新建一个动态类实例,而后加载到地图中

var layer = new ArcGISDynamicMapServiceLayer(layerobj.url);
map.addLayer(layer);
复制代码

在咱们执行setVisibleLayers时,会经过 export 方式来把对应的子图层输出图片,而后加载到地图中。setVisibleLayers(-1)关闭全部子图层。

控制动态图片的显示

/** * 改变图层的可见性 * @param {Object} dlayer 服务图层 * @param {Number} layerid 子图层 id * @param {Boolean} layervisible 可见性 */
changeLayerVisible: function (dlayer, layerid, layervisible) {
    if (dlayer == null) return;
    if (layerid < 0) return;
    var arrc = dlayer.visibleLayers;
    arrc = this.dealWithLayerInfos(arrc, dlayer.layerInfos);
    if (arrc == null || (arrc.length == 1 && arrc[0] == -1)) {
        arrc = [];
    }
    if (layervisible) {
        if (!this.checkLayerId(arrc, layerid)) {
            arrc.push(layerid);
        }
    } else {
        if (this.checkLayerId(arrc, layerid)) {
           arrc = this.removeLayerId(arrc, layerid);
        }
    }
    this.setVisibleLayers(arrc, true);
},
checkLayerId: function (arrc, layerid) {
    if (arrc == null) return false;
        for (var i = 0; i < arrc.length; i++) {
            if (arrc[i] == layerid) {
                return true;
            }
        }
    return false;
},
复制代码

回归场景

说到历史时间轴,首先要理解专题树里面的节点信息。

专题树

专题树由专题组成,每个叶子节点都是一个专题,那么专题是什么呢?

专题:专题也就是一个图层服务,每个专题layer 都是系统初始化时,经过动态服务或切片服务实例化后添加到地图中的, 每一个专题图层,都有一个图层组 layers,这个是该专题服务下的默认显示的子图层集合,因此打开或关闭专题时,若是子图层是经过动态服务加载的,也就是关闭对应的子图层。若是是切片服务的,则是关闭整个服务对象。

历史时间轴的实现

关于历史时间轴的逻辑是这样的

  1. 勾选某个图层,这个图层多是一个服务图层 Layer 也多是子图层。
  2. 遇到带有时间轴标识的,会打开时间轴面板,初始打开默认的年份。这些图层地址是经过读取配置得来。
{
      "type": "规划导则地块",
      "layers": [
        {
          "label": "2019",
          "layertype": "dynamic",
          "layername": "规划导则地块",
          "layerindex": 4,
          "serviceName": "控制性规划导则",
          "serviceUid": "",
          "defaultLayer": true,
          "url": "xxxxx/arcgis/rest/services/%E6%8E%A7%E5%88%B6%E6%80%A7%E8%A7%84%E5%88%92%E5%AF%BC%E5%88%99/MapServer/4"
        },
        {
          "label": "2018",
          "layertype": "dynamic",
          "layername": "规划导则地块",
          "layerindex": 4,
          "serviceName": "控制性规划导则",
          "serviceUid": "",
          "defaultLayer": false,
          "url": "xxxxx/arcgis/rest/services/%E6%8E%A7%E5%88%B6%E6%80%A7%E8%A7%84%E5%88%92%E5%AF%BC%E5%88%992018/MapServer"
        },
        {
          "label": "2017",
          "layertype": "dynamic",
          "layername": "规划导则地块",
          "layerindex": 4,
          "serviceName": "控制性规划导则2017",
          "defaultLayer": false,
          "url": "xxxxx/arcgis/rest/services/%E6%8E%A7%E5%88%B6%E6%80%A7%E8%A7%84%E5%88%92%E5%AF%BC%E5%88%992017/MapServer"
        },
        {
          "label": "2016",
          "layertype": "dynamic",
          "layername": "规划导则地块2016",
          "layerindex": 4,
          "serviceName": "控制性规划导则2016",
          "defaultLayer": false,
          "url": "xxxxx/arcgis/rest/services/%E6%8E%A7%E5%88%B6%E6%80%A7%E8%A7%84%E5%88%92%E5%AF%BC%E5%88%992016/MapServer"
        }
      ]
    },
复制代码
  1. 关键的切换代码
/** * _changeLayerVisible() 改变图层的显示状况 * @param {Object} layerobj 图层信息 * @param {Boolean} visible 是否可见 */
    _changeLayerVisible: function(layerobj, visible) {
      this.map = window._map; // 获取到地图

      if (!layerobj) {
        return;
      }
      if (layerobj.label != this.clashHisdata.label) {
        // 隔离与默认年份相冲突的年份,把默认年份放在else中处理
        var layerId =
          this.defaultLayer.label + '_historydataservice' + layerobj.label; // 用于同时添加多个图层 多个图层会出现
        topic.publish('history-layerIds', layerId); // 历史图层id传送至专题树,由专题书统一管理图层开关
        this.historyLayerIds.push(layerId);
        switch (layerobj.layertype) {
          case 'tiled':
            if (visible) {
              var layer = this.map.getLayer(layerId);
              if (layer) {
                this.map.removeLayer(layer);
                layer = new ArcGISTiledMapServiceLayer(layerobj.url);
                this._currentLayer = layer;
                layer.id = layerId;
                this.map.addLayer(layer);
                this.map.reorderLayer(layer, 1);
                layer.setVisibility(true);
              } else {
                layer = new ArcGISTiledMapServiceLayer(layerobj.url);
                this._currentLayer = layer;
                layer.id = layerId;
                this.map.addLayer(layer);
                this.map.reorderLayer(layer, 1);
              }
              this._currentLayer.setOpacity(this._opacity);
            } else {
              var layer = this.map.getLayer(layerId);
              if (layer) {
                //this.map.removeLayer(layer);
                layer.setVisibility(false);
              }
            }
            break;
          case 'dynamic':
            if (visible) {
              var layer = this.map.getLayer(layerId);
              if (layer) {
                if (layer.url == layerobj.url) {
                  layer.setVisibleLayers([layerobj.layerindex]);
                  this._currentLayer = layer;
                  layer.setVisibility(true);
                } else {
                  this.map.removeLayer(layer);
                  layer = new ArcGISDynamicMapServiceLayer(layerobj.url);
                  this._currentLayer = layer;
                  layer.id = layerId;
                  this.map.addLayer(layer);
                  this.map.reorderLayer(layer, 1);
                  layer.setVisibleLayers([layerobj.layerindex]);
                  layer.setVisibility(true);
                }
              } else {
                var dlayer = new ArcGISDynamicMapServiceLayer(layerobj.url);
                this._currentLayer = dlayer;
                dlayer.id = layerId;
                this.map.addLayer(dlayer);
                this.map.reorderLayer(dlayer, 1);
                dlayer.setVisibleLayers([layerobj.layerindex]);
              }
              this._currentLayer.setOpacity(1);
              setTimeout(
                lang.hitch(this, function() {
                  this._currentLayer.setOpacity(this._opacity);
                }),
                200
              );
            } else {
              var layer = this.map.getLayer(layerId);
              if (layer) {
                layer.setVisibleLayers([]);
                layer.setVisibility(false);
              }
            }
            break;
        }
      } else {
        `// 处理默认年份的显示隐藏 var layer = this.defaultLayer; if (visible) { layer.setVisibleLayers && layer.setVisibleLayers([layerobj.layerindex])`; 
            layer.setVisibility(true);
         } else {
        // 关闭
           layer.setVisibleLayers && layer.setVisibleLayers([]);
            layer.setVisibility(false); 
        }
      }
    },
复制代码

找出图层被关闭的缘由

Layer: 一个图层服务包含了不少子图层

这个是历史面板初始化时的操做。 时间轴打开后,经过serviceid去获取添加到地图中的当前 layer 对象。 从专题信息中,获取到当前专题里面的图层,默认显示的子图层。 再经过它的serviceUid 获取到加载到地图中的父图层(专题)。

图层关闭由 setVisibility(isVisible)setVisibleLayers(ids, doNotRefresh?) 组合控制,由切片服务生成的图层只有 setVisibility 属性,动态服务生成的图层则由二者组合控制图层的显示。如图,setVisibility 控制整个图层的显示,而 setVisibleLayers 能够更加细粒度地控制图层里面的子图层。

再看看以前的代码实现,经过断点发现,默认图层 layer 的子图层包含了增城导则地块图层,所以在打开默认图层的某个子图层时,这行代码layer.setVisibleLayers([layerobj.layerindex])只赋值了当前子图层的索引id,致使把以前的子图层都关闭了。

// 处理默认年份的显示隐藏
 var layer = this.defaultLayer;
if (visible) {
    `layer.setVisibleLayers && layer.setVisibleLayers([layerobj.layerindex])`; 
    layer.setVisibility(true);
 } else {
// 关闭
   `layer.setVisibleLayers && layer.setVisibleLayers([]);`
    layer.setVisibility(false); 
}
复制代码

这时候只须要添加对应的图层服务类型判断逻辑,缓存以前的就能够了。

// 处理默认年份的显示隐藏
 var layer = this.defaultLayer;
 ```var visibleLayers = []; // 可见的图层 visibleLayers = visibleLayers.concat(layer.visibleLayers);```
 if (visible) {
    // 打开
    var index = visibleLayers.indexOf(layerobj.layerindex);
    if (index === -1 && layerobj.layertype !== 'tiled') {
        visibleLayers.push(layerobj.layerindex); // 添加新的图层索引进去,不然传递 -1 会关闭全部图层
        layer.setVisibleLayers && layer.setVisibleLayers(visibleLayers); // 注意区分tiled和dynamic
    }
    layer.setVisibility(true);
 } else {
    // 知足:
    // 1. 须要解决时间轴后面的Layer 被默认的 Layer覆盖问题,
    // 2. 可是不能把整个 Layer 关闭了,不然,会影响属于同一个图层服务实例下,其余子图层的显示。
    // 3. 当前只能把后面的图层移动 z-index,可是又要知足时间轴的图层做为底图来使用。
    `if (layerobj.layertype === 'tiled') { // 判断是否为切片 layer.setVisibility(false); } else { // 动态图片服务的关闭 // 关闭 var index = visibleLayers.indexOf(layerobj.layerindex); if (index > -1) { visibleLayers.splice(index, 1); } layer.setVisibleLayers && layer.setVisibleLayers(visibleLayers); }`
  }
}
复制代码

固然,上面的默认图层的显示/隐藏,能够直接把图层对象传递给专题树来统一处理开关,这样就不用写这些判断逻辑了。

(全文完)

总结

  • 关键是理解图层的概念以及它们是怎么加载的,这样遇到控制图层的问题时就能快速定位解决了。
  • 目前的 arcgis api 是通过优化的,只有地图范围改变了,才会去请求地图服务。因此在切换时间轴时,要注意改变它的范围,才能更好的定位错误。

参考资料

相关文章
相关标签/搜索