GIS图层概念及应用

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

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

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

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

什么是图层

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

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

所有的图层都是从Layer类型继承而来的,可以参考下载的API中的对象模型图。

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

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

图层的加载

什么是地图服务

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

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

下面说说常见的两种图层加载模式,实例化一个图层对象,需要传入图层的 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);
    }`
  }
}
复制代码

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

(全文完)

我来评几句
登录后评论

已发表评论数()

相关站点

+订阅
热门文章