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 是通過優化的,只有地圖範圍改變了,纔會去請求地圖服務。因此在切換時間軸時,要注意改變它的範圍,才能更好的定位錯誤。

參考資料

相關文章
相關標籤/搜索