Cesium 入门(三):BaseLayerPicker 底图切换与国产地图接入
前两篇我们把三维地球跑起来,并把搜索框接入了天地图。这一篇来聊聊底图(Imagery) ------Cesium 自带的
BaseLayerPicker怎么用,以及如何把高德、天地图等国产底图整合进去,做成一个可切换的底图选择器。
一、Cesium 自带的 BaseLayerPicker
Cesium Viewer 有一个非常方便的内置控件:baseLayerPicker。只要在初始化时把它打开:
js
viewer = new Cesium.Viewer('cesium-container', {
baseLayerPicker: true,
// ...
})
地图右上角就会出现一个底图选择按钮,点击后展开 Cesium 默认提供的底图列表:

默认列表主要来自 Cesium ion,包括 Bing Maps、Google Maps、Sentinel-2、ArcGIS、OpenStreetMap 等。对海外数据来说很方便,但在国内项目中往往不够接地气:
- 国产卫星影像、矢量电子地图不够精细;
- 部分服务在国内访问不稳定;
- 项目需要统一用天地图/高德作为合规底图。
所以下一步就是:把国产底图也注册进 BaseLayerPicker。
二、核心概念:ProviderViewModel
BaseLayerPicker 里每一个可选的底图,本质上是一个 Cesium.ProviderViewModel 实例。它的核心结构是:
js
new Cesium.ProviderViewModel({
name: '显示名称',
tooltip: '悬停提示',
iconUrl: '缩略图 data URI',
category: '分组名',
creationFunction: () => ImageryProvider | ImageryProvider[],
})
关键点是 creationFunction:
- 返回一个
ImageryProvider时,表示只有一层底图; - 返回一个数组时,数组顺序就是图层叠放顺序,Cesium 会把整组图层一起切换。
这就为我们做"影像 + 注记"的分层底图提供了便利------比如天地图的卫星影像 img 在下,地名注记 cia 在上。
三、接入高德底图
高德地图的瓦片可以通过 UrlTemplateImageryProvider 直接加载,不需要走 JS API,因此也不需要 key( key 只在调用高德 Web 服务接口时才需要)。
高德常用的三种瓦片风格:
| style | 含义 | 用法 |
|---|---|---|
6 |
卫星影像 | 需要叠加 8 路网注记 |
7 |
矢量地图 | 自带路网和地名注记 |
8 |
路网注记 | 通常叠加在 6 上使用 |
封装一下:
js
function createAmapLayer(style) {
return new Cesium.UrlTemplateImageryProvider({
url: `https://webst0{s}.is.autonavi.com/appmaptile?style=${style}&x={x}&y={y}&z={z}`,
subdomains: ['1', '2', '3', '4'],
maximumLevel: 18,
})
}
四、天地图底图回顾
天地图我们第一篇已经用过,这里快速回顾一下 WMTS 图层:
| layerName | 含义 |
|---|---|
img |
卫星影像底图 |
vec |
矢量地图底图 |
ter |
地形晕渲底图 |
cia |
影像注记(叠加在 img 上) |
cva |
矢量注记(叠加在 vec/ter 上) |
封装(token 用占位符):
js
function createTiandituLayer(layerName) {
return new Cesium.WebMapTileServiceImageryProvider({
url: `https://t{s}.tianditu.gov.cn/${layerName}_w/wmts?service=wmts&request=GetTile&version=1.0.0&LAYER=${layerName}&tileMatrixSet=w&TileMatrix={TileMatrix}&TileRow={TileRow}&TileCol={TileCol}&style=default&format=tiles&tk=${TIANDITU_TOKEN}`,
layer: layerName,
style: 'default',
format: 'tiles',
tileMatrixSetID: 'w',
subdomains: ['0', '1', '2', '3', '4', '5', '6', '7'],
maximumLevel: 18,
})
}
五、生成缩略图图标
ProviderViewModel 需要一个 iconUrl。为了不用准备一堆图片,可以用 SVG 动态生成 data URI:
js
function icon(bg, text) {
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64"><rect width="64" height="64" rx="8" fill="${bg}"/><text x="32" y="41" font-size="22" text-anchor="middle" fill="#fff" font-family="sans-serif">${text}</text></svg>`
return `data:image/svg+xml,${encodeURIComponent(svg)}`
}
这样每个底图选项都能有一个带文字的小方块图标,配合 category 字段自动分组。
六、完整代码:自定义底图选择器
下面是完整的 App.vue。关键逻辑:
- 用
customImageryViewModels注册高德、天地图底图; - 把它和
Cesium.createDefaultImageryProviderViewModels()合并; - 通过
imageryProviderViewModels传给 Viewer; - 通过
terrainProviderViewModels保留地形切换能力; - 默认选中第一个国产底图(高德卫星)。
vue
<script setup>
import * as Cesium from 'cesium'
import { onBeforeUnmount, onMounted } from 'vue'
// 高德 key(瓦片直连不需要 key;调用高德 Web 服务 API 时才用到)
const AMAP_KEY = '*****'
const AMAP_SECRET = '*****'
// 天地图 token(底图 WMTS 都要用)
const TIANDITU_TOKEN = '*****'
let viewer = null
// 创建高德底图图层
// style: 6-卫星影像 / 7-矢量地图 / 8-路网注记
function createAmapLayer(style) {
return new Cesium.UrlTemplateImageryProvider({
url: `https://webst0{s}.is.autonavi.com/appmaptile?style=${style}&x={x}&y={y}&z={z}`,
subdomains: ['1', '2', '3', '4'],
maximumLevel: 18,
})
}
// 创建天地图 WMTS 图层
// layerName: img-影像 / vec-矢量 / ter-地形晕渲 / cia-影像注记 / cva-矢量注记
function createTiandituLayer(layerName) {
return new Cesium.WebMapTileServiceImageryProvider({
url: `https://t{s}.tianditu.gov.cn/${layerName}_w/wmts?service=wmts&request=GetTile&version=1.0.0&LAYER=${layerName}&tileMatrixSet=w&TileMatrix={TileMatrix}&TileRow={TileRow}&TileCol={TileCol}&style=default&format=tiles&tk=${TIANDITU_TOKEN}`,
layer: layerName,
style: 'default',
format: 'tiles',
tileMatrixSetID: 'w',
subdomains: ['0', '1', '2', '3', '4', '5', '6', '7'],
maximumLevel: 18,
})
}
// 生成 BaseLayerPicker 缩略图图标(SVG 转 data URI)
function icon(bg, text) {
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64"><rect width="64" height="64" rx="8" fill="${bg}"/><text x="32" y="41" font-size="22" text-anchor="middle" fill="#fff" font-family="sans-serif">${text}</text></svg>`
return `data:image/svg+xml,${encodeURIComponent(svg)}`
}
// 自定义底图选项。creationFunction 返回数组时,数组顺序 = 叠放顺序
const customImageryViewModels = [
new Cesium.ProviderViewModel({
name: '高德卫星影像',
tooltip: '高德卫星影像 + 路网注记',
iconUrl: icon('#1b2a4a', '卫星'),
category: '高德地图',
creationFunction: () => [createAmapLayer(6), createAmapLayer(8)],
}),
new Cesium.ProviderViewModel({
name: '高德矢量地图',
tooltip: '高德矢量地图(自带路网注记)',
iconUrl: icon('#2e7d32', '矢量'),
category: '高德地图',
creationFunction: () => [createAmapLayer(7)],
}),
new Cesium.ProviderViewModel({
name: '天地图卫星影像',
tooltip: '天地图卫星影像 + 地名注记',
iconUrl: icon('#37474f', '天卫'),
category: '天地图',
creationFunction: () => [
createTiandituLayer('img'),
createTiandituLayer('cia'),
],
}),
new Cesium.ProviderViewModel({
name: '天地图矢量地图',
tooltip: '天地图矢量地图 + 地名注记',
iconUrl: icon('#1565c0', '天矢'),
category: '天地图',
creationFunction: () => [
createTiandituLayer('vec'),
createTiandituLayer('cva'),
],
}),
new Cesium.ProviderViewModel({
name: '天地图地形晕渲',
tooltip: '天地图地形晕渲 + 地名注记',
iconUrl: icon('#8d6e63', '地形'),
category: '天地图',
creationFunction: () => [
createTiandituLayer('ter'),
createTiandituLayer('cva'),
],
}),
]
onMounted(() => {
// 默认底图列表 + 自定义国产底图
const imageryViewModels = [
...customImageryViewModels,
...Cesium.createDefaultImageryProviderViewModels(),
]
viewer = new Cesium.Viewer('cesium-container', {
baseLayerPicker: true,
imageryProviderViewModels: imageryViewModels,
selectedImageryProviderViewModel: imageryViewModels[0], // 默认高德卫星
// 地形切换:椭球(无地形)+ Cesium 全球地形
terrainProviderViewModels: Cesium.createDefaultTerrainProviderViewModels(),
animation: false,
timeline: false,
geocoder: false,
homeButton: false,
sceneModePicker: false,
navigationHelpButton: false,
fullscreenButton: false,
})
window.viewer = viewer
window.Cesium = Cesium
viewer.camera.flyTo({
destination: Cesium.Cartesian3.fromDegrees(116.39, 39.9, 50000),
})
})
onBeforeUnmount(() => {
if (viewer) {
viewer.destroy()
viewer = null
}
})
</script>
<template>
<div id="cesium-container"></div>
</template>
<style scoped>
#cesium-container {
width: 100%;
height: 100%;
}
</style>
运行效果:
国产底图被放在了最前面,并按 category 分组为"高德地图"、"天地图":

默认的 Cesium ion 底图仍然保留,作为可选补充:

下方还有 Terrain(地形)分组,可以在"无地形"和"Cesium World Terrain"之间切换:

七、关键细节与踩坑提醒
1. creationFunction 返回数组的含义
返回数组时,Cesium 会按数组顺序把图层逐个加入 imageryLayers。所以卫星影像要放前面,注记图层放后面:
js
creationFunction: () => [
createTiandituLayer('img'), // 底图
createTiandituLayer('cia'), // 注记,叠在底图之上
]
如果顺序写反了,注记会被影像盖住。
2. category 决定分组
相同 category 的 ProviderViewModel 会自动归到同一组。中文 category 可以直接显示,不需要额外转码。
3. imageryProviderViewModels 和 selectedImageryProviderViewModel
imageryProviderViewModels:传入你想让用户看到的所有底图选项;selectedImageryProviderViewModel:页面加载时默认选中的那一项,必须是imageryProviderViewModels数组里的某个实例。
4. 地形和影像分开配置
baseLayerPicker 同时控制影像底图和地形。影像通过 imageryProviderViewModels 配置,地形通过 terrainProviderViewModels 配置。两者互不影响,可以分别扩展。
5. Token 安全
天地图 token 必须传入。高德瓦片直连不需要 key,但为了代码结构完整,通常还是会保留 AMAP_KEY 变量。发布前请把示例里的 '*****' 替换为真实值,并且不要直接提交到公网仓库,建议通过环境变量注入。
八、写在最后
这一篇我们把 Cesium 的 BaseLayerPicker 从"默认 Bing/Google 列表"扩展成了"国产底图 + 默认底图"的混合选择器。核心思路就是:
把每一种底图封装成
ProviderViewModel,通过creationFunction返回单图层或多图层组合,再用category做分组。
3D Tiles、倾斜摄影等专题数据我们会单独放到后面的文章里讲,因为它们不属于"底图"范畴,而是叠加在底图之上的三维模型层。
本文是《Cesium 入门》系列第 3 篇。示例代码中的 TIANDITU_TOKEN、AMAP_KEY、AMAP_SECRET 均为占位符,运行前请替换为你自己的真实密钥。