前言:
在上一篇《Cesium 实现行政区反向遮罩镂空效果》中,我们实现了全局暗色蒙层 + 行政区镂空高亮的效果:外部地图变暗、目标行政区正常显示。 本文针对上一篇文章的进阶,实现相关的反向需求:外部地图保持正常清晰显示,仅目标行政区内部叠加暗色遮罩弱化。
演示动态图示如下:
上一篇和本篇的两种效果对比:
上一篇: 全球大矩形 + holes 挖洞 → 外暗、内亮(Geojson数据的区域高亮)
本篇: 直接绘制Geojson数据的区域的Polygon填充 → 外亮、内暗(Geojson数据的区域弱化遮罩)
核心实现原理:
- 读取行政区 GeoJSON 边界坐标。
- 扁平化多维嵌套坐标,转为 Cesium 可识别笛卡尔坐标。
- 直接基于行政区轮廓生成贴地多边形,填充半透明黑色遮罩。
- 叠加蓝色边界描边,保证区域轮廓清晰。
完整代码
1. template 结构:
<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 代码:
<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);
// 组件销毁生命周期:释放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);
drawInnerMask(songjiang);
isLoading.value = true;
flyTo();
};
const drawInnerMask = (dataHoleList, 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);
// 直接绘制Geojson数据的区面,填充遮罩
let districtMask = {
id: 'districtMask',
name: '行政区内部遮罩',
polygon: {
hierarchy: {
positions: holes
},
material: maskColor,
fill: true,
clampToGround: true // 贴地
}
};
window.viewer.entities.add(districtMask);
// 绘制Geojson数据的边界轮廓线
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样式代码:
.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;
}
为什么不用 holes 挖洞做反向效果?
Cesium 的 PolygonHierarchy.holes 机制逻辑固定:外层坐标为实体面,holes 数组永远是挖空区域,不存在“反转镂空”参数。想要区内变暗,直接绘制Geojson数据的区域面是唯一优雅、无bug的方案。
总结:
本文基于上一篇《Cesium 实现行政区反向遮罩镂空效果》完成了绘制Geojson数据的区域的内部遮罩的进阶改造,补齐了 Cesium 行政区可视化的两种主流遮罩方案。
本文案例中使用的高德瓦片资源仅用于学习、技术研究演示,不作线上商用部署场景使用。若企业项目正式上线使用同类地图瓦片资源,请自行前往对应地图服务商平台完成资质认证并申请合规调用密钥。