前言
Cesium 实现行政区反向遮罩效果,基于 GeoJSON 边界数据,本例中GeoJSON数据为上海市松江区的地理数据。本例效果展示上海松江区范围内地图,地图上超出上海松江区的区域使用半透明暗色画布遮盖;并且本例中的底图支持一键切换矢量瓦片底图与带标注影像底图。
演示动态图示如下:

实现效果:
- 全局大范围暗色蒙层覆盖地图。
- 读取 GeoJSON 行政区边界,在对应区域「镂空挖洞」。
- 仅目标行政区正常显示底图,视觉焦点高度集中。
- 支持一键切换「矢量底图 / 带注记影像底图」。
核心原理讲解:
1、Cesium 反向遮罩实现原理:
通过 polygon -> hierarchy -> holes 实现 "地图挖洞":
- 绘制一个覆盖全球范围的超大矩形面作为底层遮罩。
- 将目标行政区 GeoJSON 边界坐标,作为该面的孔洞数组 holes。
- 底层填充半透明黑色,孔洞区域透明透底,实现「外暗内亮」。
2、GeoJSON 多维数组扁平化:
标准行政区 GeoJSON 坐标是多层嵌套数组,Cesium 无法直接解析,需要递归扁平化处理成一维经纬度数组。
3、双底图切换逻辑:
影像模式采用「影像图层 + 文字注记图层」双层叠加,保证卫星图也能看到道路、地名;矢量模式单一图层渲染,保证简洁清爽。切换时清空所有图层再重绘,避免图层叠加残留。
完整代码
1. template 结构:
xml
<template>
<div class="main">
<!-- 主地图渲染容器 -->
<div class="content" ref="content" id="earth"></div>
<!-- 功能按钮区域,地图加载完成后展示 -->
<div class="btn-border" v-if="isLoading">
<!-- 镜头复位:飞行至初始视角 -->
<el-button type="primary" size="default" class="btn" @click="flyTo">初始位置</el-button>
<!-- 底图切换按钮:矢量底图 / 影像底图互切 -->
<el-button type="primary" size="default" class="btn" @click="changeMap">{{isImagery ? '切换矢量底图' : '切换影像底图'}}</el-button>
</div>
</div>
</template>
2. script 代码:
xml
<script setup>
import { onMounted, nextTick, ref, onUnmounted } from 'vue';
import { token } from '../../utils/common.js';
import { ElMessage } from 'element-plus';
// 上海松江区边界GeoJSON地理数据
import songjiang from '../../assets/songjiang.json';
// 地图加载完成标识
let isLoading = ref(false);
// 底图类型标记:true=影像底图,false=矢量底图
let isImagery = ref(true);
// 全局遮罩外框坐标数组,生成反向遮罩(外部区域暗色遮盖,内部行政区镂空显示)
let maskDataList = [55.40046568, 64.81241339999998, 176.64339637039996, 64.81241339999998, 176.64339637039998, -4.708978609400006, 55.40046568000002, -4.708978609400044];
// 组件销毁生命周期:释放Viewer实例,清理WebGL资源,防止内存泄漏
onUnmounted(() => {
if (window.viewer) {
window.viewer.destroy();
window.viewer = null;
}
});
onMounted(() => {
nextTick(() => {
initMap();
});
});
// 初始化主Cesium三维地图
const initMap = () => {
// 设置 Cesium Ion 的token
Cesium.Ion.defaultAccessToken = token;
// 设置默认视角范围(中国区域)
Cesium.Camera.DEFAULT_VIEW_RECTANGLE = Cesium.Rectangle.fromDegrees(89.5, 20.4, 110.4, 61.2);
// 实例化主地图Viewer
window.viewer = new Cesium.Viewer('earth', {
animation: false, // 时间动画控件
timeline: false, // 时间轴
infoBox: false, // 点击要素弹窗
geocoder: false, // 搜索框
homeButton: false, // 复位视角按钮
sceneModePicker: false, // 2D/3D切换按钮
baseLayerPicker: false, // 底图切换面板
navigationHelpButton: false, // 操作帮助弹窗
fullscreenButton: false, // 全屏按钮
selectionIndicator: false, // 选中要素高亮框
shouldAnimate: false // 关闭自动动画渲染,节省性能
});
// 初始化影像底图:影像图层 + 道路文字注记图层叠加
let layer1 = new Cesium.UrlTemplateImageryProvider({
url: "https://webst02.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}",
minimumLevel: 4,
maximumLevel: 18
});
window.viewer.imageryLayers.addImageryProvider(layer1);
let layer2 = new Cesium.UrlTemplateImageryProvider({
url: "http://webst02.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scale=1&style=8",
minimumLevel: 4,
maximumLevel: 18
});
window.viewer.imageryLayers.addImageryProvider(layer2);
// 绘制反向遮罩:本例中仅松江区区域正常显示,外部区域暗色遮罩
drawMask(songjiang, maskDataList);
isLoading.value = true;
flyTo();
};
const drawMask = (dataHoleList, maskDataList, dataBorderColor = new Cesium.Color.fromBytes(0, 0, 255, 255), maskColor = new Cesium.Color.fromBytes(0, 0, 0, 200)) => {
// 读取GeoJSON内多边形坐标
let holeList = dataHoleList.features[0].geometry.coordinates;
// 递归扁平化多维坐标数组
let holes = dealArr(holeList);
// 经纬度数组转为笛卡尔坐标
holes = Cesium.Cartesian3.fromDegreesArray(holes);
// 创建遮罩多边形,使用hierarchy实现挖洞效果
let maskPolygon = {
id: 'maskPolygon',
name: '遮罩层',
show: true,
polygon: {
hierarchy: {
positions: Cesium.Cartesian3.fromDegreesArray(maskDataList),
holes: [{ positions: holes }] // 挖空松江区范围,该区域不被遮挡
},
material: maskColor,
fill: true
}
};
window.viewer.entities.add(maskPolygon);
// 绘制松江区边界轮廓线
let maskLine = {
polyline: {
positions: holes,
width: 5,
material: dataBorderColor,
clampToGround: true // 贴地绘制
}
};
window.viewer.entities.add(maskLine);
};
// 切换底图类型的方法:矢量底图 <==> 影像底图(影像叠加道路注记)
const changeMap = () => {
// 防护:地图实例不存在直接退出
if (!window.viewer) {
return;
}
// 清空当前所有影像图层
window.viewer.imageryLayers.removeAll();
if (!isImagery.value) {
// 切换为影像底图:影像图层 + 道路文字注记图层叠加
let layer1 = new Cesium.UrlTemplateImageryProvider({
url: "https://webst02.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}",
minimumLevel: 4,
maximumLevel: 18
});
window.viewer.imageryLayers.addImageryProvider(layer1);
let layer2 = new Cesium.UrlTemplateImageryProvider({
url: "http://webst02.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scale=1&style=8",
minimumLevel: 4,
maximumLevel: 18
});
window.viewer.imageryLayers.addImageryProvider(layer2);
} else {
// 切换回矢量底图
let layer = new Cesium.UrlTemplateImageryProvider({
url: "http://webrd02.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}",
minimumLevel: 4,
maximumLevel: 18
});
window.viewer.imageryLayers.addImageryProvider(layer);
}
// 切换状态标识
isImagery.value = !isImagery.value;
};
// 递归扁平化多维坐标数组的方法
const dealArr = (arr) => {
let newArrFun = function (arr) {
return arr.reduce((pre, cur) => {
return pre.concat(Array.isArray(cur) ? newArrFun(cur) : cur)
}, [])
}
let newArr = newArrFun(arr);
return newArr;
}
// 相机飞行至预设初始视角(上海松江区域)的方法
const flyTo = () => {
window.viewer.camera.flyTo({
destination: Cesium.Cartesian3.fromDegrees(121.21709895255839, 31.01351793218674, 66403.24207453332),
orientation: {
heading: Cesium.Math.toRadians(359.8090548395541), // 航向角
pitch: Cesium.Math.toRadians(-89.63236031882052), // 俯仰角
roll: Cesium.Math.toRadians(0) // 翻滚角
},
duration: 3 // 飞行时长3秒
});
};
</script>
3. css样式代码:
css
.main {
width: 100%;
height: 100vh;
position: relative;
}
.content {
width: 100%;
height: 100%;
position: relative;
z-index: 1;
}
.btn-border {
position: absolute;
right: 24px;
top: 24px;
z-index: 2;
display: flex;
justify-content: start;
align-items: stretch;
}
.btn {
margin-left: 20px;
cursor: pointer;
}
关键踩坑总结
1、挖洞不生效、遮罩全覆盖
大概率是 GeoJSON 坐标环方向问题(顺时针/逆时针)。Cesium 对孔洞坐标环方向严格校验,坐标顺序错误会导致镂空失效。
2、底图切换残留图层
必须使用 imageryLayers.removeAll() 清空所有图层再重绘,不能直接覆盖,否则会出现图层叠加、透明度异常。
3、GeoJSON多维坐标解析失败
行政区域、复杂多边形 GeoJSON 一定是多层嵌套,必须递归扁平化,否则 fromDegreesArray 解析报错、图形不显示。
本文案例中使用的高德瓦片资源仅用于学习、技术研究演示,不作线上商用部署场景使用。若企业项目正式上线使用同类地图瓦片资源,请自行前往对应地图服务商平台完成资质认证并申请合规调用密钥。