Featured image of post 图层操作指南:添加、搜索、移除与图层管理

图层操作指南:添加、搜索、移除与图层管理

欢迎来到 WebGIS 入门系列的第八篇文章!在本文中,我们将详细介绍 ArcGIS Maps SDK for JavaScript 中的图层操作。我们将深入探讨地图和图层之间的关系,包括底图的概念,以及如何获取图层初始化所需的 URL 链接,文章最后带大家开发一个图层管理组件。

地图与图层的关系

在 WebGIS 中,地图通常是由一个或多个图层叠加而成的。其中,底图是地图中的底层图层,通常我们称之为“底图图层”,提供地图的基础背景,例如道路、河流和地形等。而其他图层则可以是各种类型的地理数据,如要素图层、栅格图层、动态图层等,为了便于将其与底图图层作区分,我们称之为“业务图层”。

地图与图层的关系

添加图层

要向地图中添加图层,我们需要先创建图层对象,然后使用 Map 对象的 add() 方法将其添加到地图中。在创建图层时,我们通常需要提供图层的数据来源 URL。 以下是一个示例,演示如何添加一个要素图层到地图中:

const map = new Map({
  basemap: "streets", // 添加底图
})

const featureLayer = new FeatureLayer({
  url: "https://services.arcgis.com/P3ePLMYs2RVChkJx/arcgis/rest/services/USA_Congressional_Districts/FeatureServer/0", // 添加要素图层的 URL
  id: "feature-1",
})

map.add(featureLayer) // 向地图中添加要素图层

获取图层初始化 URL 链接

在上述示例中,我们使用了一个示例的要素图层 URL。实际应用中,您可以从各种来源获取图层的初始化 URL 链接,包括 ArcGIS Online 、ArcGIS Enterprise、开放数据集等。通常,这些 URL 链接将提供图层的元数据信息、渲染样式、属性数据等。 如果你是开发人员,并且兼职 GIS 数据处理工作,那这个 URL 链接通常是你将数据导入 ArcGIS Pro 或 ArcMap,并且按项目需求处理发布到 ArcGIS Server、ArcGIS Online 或 ArcGIS Enterprise 中之后获得的数据服务链接(具体操作本系列后续文章会介绍)。

搜索、移除图层

要从地图中移除图层,您可以使用 Map 对象的 remove() 方法。以下是一个示例:

map.remove(featureLayer) // 从地图中移除要素图层

上述 remove() 方法的传参是一个图层对象,即你要删除的图层。此图层在删除之前首先要找到该图层,在 ArcGIS Maps SDK for JavaScript 中要搜索一个图层,可以通过实例化该图层时的 ID 值来搜索,如下:

const resultLayer = map.findLayerById("feature-1")

设置图层透明度

您可以通过设置图层的 opacity 属性来调整图层的透明度。透明度的值范围从 0(完全透明)到 1(完全不透明)。

featureLayer.opacity = 0.5 // 设置要素图层的透明度为 50%

上述内容是在 ArcGIS Maps SDK for JavaScript 中关于图层相关的最基础、也是日常项目中需求频率最高的操作,但是 ArcGIS Maps SDK for JavaScript 中有 40 多种图层 API,每种图层 API 都有各自特定的属性和操作,希望各位在实际开发中详细阅读相应的 API 文档。

小作业:实现图层列表组件

让我们尝试实现一个简单的图层列表管理组件,用于在地图中添加和移除图层。 首先,我们在 src/stores/counter.ts 文件中创建一个 useMapViewStore 对象,用于在 Pinia 中存储创建好的 mapView 并且可以在全局使用,代码如下:

export const useMapViewStore = defineStore("mapView", () => {
  const mapView = ref({})
  function setMapView(val: MapViewData) {
    mapView.value = markRaw(val) //mapView.value = val
  }

  return { mapView, setMapView }
})

修改原有 MapView 组件中的代码,使其在创建完 view 对象之后将其存储到 useMapViewStore 中,代码如下:

// ......
import { useMapViewStore } from "@/stores/counter"
// ......

const mapViewStore = useMapViewStore()

const initMap = () => {
  // ......

  const view = new MapView({
    map,
    container: "map",
  })
  mapViewStore.setMapView(view) // ......
}

onMounted(() => {
  initMap()
})

src/components 目录下创建 LayerList 组件,在此组件中我们初始化图层列表,并结合上述所介绍的内容来实现图层的添加和卸载,代码如下:

<template>
   
  <div class="layer-list">
       
    <div class="layer-list-icon" @click="handleLayerListPanelVisible">
            <img :src="LayerListIcon" />    
    </div>
       
    <div class="layer-list-view" v-show="layerListPanelVisible">
           
      <div class="layer-list-herder">
                <span>业务图层列表</span>      
      </div>
           
      <div class="layer-list-content">
               
        <div class="layer-list-item" v-for="layer in layerList" :key="layer.id">
                    <span>{{ layer.name }}</span>          
          <img
            :src="alreadyAddedLayerIds.includes(layer.id) ? LayerCloseIcon : LayerOpenIcon"
            @click="handleLayerItemClick(layer)"
          />
                 
        </div>
             
      </div>
         
    </div>
     
  </div>
</template>

<script setup lang="ts">
  import { ref } from "vue"
  import MapImageLayer from "@arcgis/core/layers/MapImageLayer.js"
  import FeatureLayer from "@arcgis/core/layers/FeatureLayer.js"
  import ImageryLayer from "@arcgis/core/layers/ImageryLayer.js"
  import TileLayer from "@arcgis/core/layers/TileLayer.js"
  import { useMapViewStore } from "@/stores/counter"
  import type { MapViewData, LayerDataItem } from "@/interface/index"
  import LayerListIcon from "./icons/layer-list-icon.svg"
  import LayerOpenIcon from "./icons/layer-open.svg"
  import LayerCloseIcon from "./icons/layer-close.svg"

  const mapViewStore = useMapViewStore()

  const layerListPanelVisible = ref(false)
  const layerList: LayerDataItem[] = [
    {
      id: "layer-1",
      name: "map-image-layer",
      url: "https://sampleserver6.arcgisonline.com/arcgis/rest/services/USA/MapServer",
      type: "MapImage",
    },
    {
      id: "layer-2",
      name: "feature-layer",
      url: "https://sampleserver6.arcgisonline.com/arcgis/rest/services/USA/MapServer/0",
      type: "Feature",
    },
    {
      id: "layer-3",
      name: "imagery-layer",
      url: "https://landsat2.arcgis.com/arcgis/rest/services/Landsat8_Views/ImageServer",
      type: "Imagery",
    },
    {
      id: "layer-4",
      name: "tile-layer",
      url: "https://services.arcgisonline.com/arcgis/rest/services/World_Terrain_Base/MapServer",
      type: "Tile",
    },
  ]
  const alreadyAddedLayerIds = ref<string[]>([])

  function handleLayerListPanelVisible() {
    layerListPanelVisible.value = !layerListPanelVisible.value
  }

  function handleLayerItemClick(layer: LayerDataItem) {
    let ArcGISLayerAPI
    switch (layer.type) {
      case "MapImage":
        ArcGISLayerAPI = MapImageLayer
        break
      case "Feature":
        ArcGISLayerAPI = FeatureLayer
        break
      case "Imagery":
        ArcGISLayerAPI = ImageryLayer
        break
      case "Tile":
        ArcGISLayerAPI = TileLayer
        break
      default:
        break
    } // 初始化图层之前判断当前图层是否已添加

    const mapView = mapViewStore.mapView as MapViewData
    const layers = mapView.map.layers.items
    const layerIds = layers.map((layer) => layer.id)
    if (layerIds.includes(layer.id)) {
      // 卸载图层
      const resultLayer = mapView.map.findLayerById(layer.id)
      if (resultLayer) {
        mapView.map.remove(resultLayer) // 卸载完图层之后更新 alreadyAddedLayerIds state

        const layers = mapView.map.layers.items
        const layerIds = layers.map((layer) => layer.id)
        alreadyAddedLayerIds.value = layerIds
      }
    } else {
      // 添加图层
      if (ArcGISLayerAPI) {
        const layerRes = new ArcGISLayerAPI({
          url: layer.url,
          id: layer.id,
        })
        mapView.map.add(layerRes) // 添加完图层之后更新 alreadyAddedLayerIds state

        const layers = mapView.map.layers.items
        const layerIds = layers.map((layer) => layer.id)
        alreadyAddedLayerIds.value = layerIds
      }
    }
  }
</script>

<style scoped>
  .layer-list-icon {
    position: absolute;
    top: 16px;
    right: 16px;
    width: 36px;
    height: 36px;
    padding: 2px;
    background-color: #ffffff;
    border: 2px solid #bfbfbf;
    box-sizing: border-box;
    cursor: pointer;
  }
  .layer-list-icon:hover {
    border-color: #4096ff;
  }
  .layer-list-icon img {
    width: 100%;
    height: 100%;
  }

  .layer-list-view {
    position: absolute;
    top: 16px;
    right: 60px;
    width: 260px;
    height: 400px;
    padding: 0 16px;
    background-color: rgba(255, 255, 255, 0.85);
    border: 1px solid #bfbfbf;
    box-sizing: border-box;
  }
  .layer-list-herder {
    height: 48px;
    display: flex;
    align-items: center;
    border-bottom: 1px solid #bfbfbf;
    box-sizing: border-box;
  }
  .layer-list-herder span {
    font-size: 16px;
    font-weight: 600;
  }
  .layer-list-content {
    height: calc(100% - 48px);
    padding-top: 8px;
  }
  .layer-list-item {
    height: 40px;
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding-right: 8px;
    box-sizing: border-box;
  }
  .layer-list-item > img {
    width: 16px;
    height: 16px;
    cursor: pointer;
  }
  .layer-list-item:hover {
    background-color: #bfbfbf;
    padding-left: 8px;
    font-weight: 600;
  }
  .layer-list-item:hover > img {
    width: 24px;
    height: 24px;
  }
</style>

为了代码的整洁和初学者阅读,有些繁琐的代码并没有做过多的抽象处理,同时也没有用过多的第三方库来简化代码,但是针对 TS 类型定义这种不会影响到学习的事情,我们在 src 目录下新建了 interface 目录,并将项目中所用到的 TS 类型全部定义在了此处:

export type LayerDataItem = {
  id: string
  name: string
  url: string
  type: string
}

export type MapViewData = {
  map: {
    layers: {
      items: Array<LayerDataItem>
    }
    add: Function
    findLayerById: Function
    remove: Function
  }
}

通过上述操作,我们最终实现了一个简易版本的图层管理组件,并且实现了将我们的业务数据按照图层列表的形式进行了展示,也可以方便的实现添加和卸载操作,同时,您可以加深对图层操作的理解,并且学会如何在 Vue.js 中创建一个图层列表组件。

图层列表组件

结语

通过本文的指导,您已经详细了解了如何在 ArcGIS Maps SDK for JavaScript 中进行图层操作,包括添加、移除和设置透明度。同时,您也实践了一个简单的图层列表组件。希望本文能够帮助您更好地掌握图层操作技巧,并且启发您创造出更加丰富和实用的 WebGIS 地图界面。祝您在 WebGIS 开发的旅程中取得成功!

后续优化

考虑到后续功能模块的界面布局,目前调整 LayerList 组件的样式,点击图层管理图标之后,图层列表面板将在图标下方弹出,所以优化 LayerList.vue 文件中 layer-list-view 的 css 样式:

.layer-list-view {
  // ......
  top: 60px;
  right: 16px;
  // ......
}

图层列表组件优化