欢迎来到 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;
// ......
}