The Other Cesium Mask: Darkening a District Interior While Keeping the Map Clear
Preface:
In the previous article "Cesium Implements Reverse Masking and Hollowing for Administrative Districts," we achieved the effect of a global dark overlay + administrative district hollow highlight: the outer map darkens, and the target administrative district displays normally. This article builds on the previous one to implement the related reverse requirement: the outer map remains clear and normal, while only the interior of the target administrative district is overlaid with a dark mask to weaken it.
The dynamic demonstration is shown below:
Comparison of the two effects from the previous and current articles:
Previous article: Global large rectangle + holes cutout → dark outside, bright inside (the GeoJSON data area is highlighted)
Current article: Directly draw a Polygon fill of the GeoJSON data area → bright outside, dark inside (the GeoJSON data area is a weakened mask)
Core Implementation Principles:
- Read the administrative district GeoJSON boundary coordinates.
- Flatten the multi-dimensional nested coordinates and convert them into Cartesian coordinates recognizable by Cesium.
- Directly generate a ground-clamped polygon based on the administrative district outline, filled with a semi-transparent black mask.
- Overlay a blue boundary outline to ensure the area contour is clear.
Complete Code
1. Template structure:
<template>
<div class="main">
<!-- Main map rendering container -->
<div class="content" ref="content" id="earth"></div>
<!-- Function button area, displayed after the map is loaded -->
<div class="btn-border" v-if="isLoading">
<!-- Camera reset: fly to initial view -->
<el-button type="primary" size="default" class="btn" @click="flyTo">Initial Position</el-button>
<!-- Base map toggle button: vector base map / imagery base map switch -->
<el-button type="primary" size="default" class="btn" @click="changeMap">{{isImagery ? 'Switch to Vector Map' : 'Switch to Imagery Map'}}</el-button>
</div>
</div>
</template>
2. Script code:
<script setup>
import { onMounted, nextTick, ref, onUnmounted } from 'vue';
import { token } from '../../utils/common.js';
import { ElMessage } from 'element-plus';
// Shanghai Songjiang District boundary GeoJSON geographic data
import songjiang from '../../assets/songjiang.json';
// Map loading completion flag
let isLoading = ref(false);
// Base map type flag: true=imagery base map, false=vector base map
let isImagery = ref(true);
// Component unmount lifecycle: release Viewer instance, clean up WebGL resources, prevent memory leaks
onUnmounted(() => {
if (window.viewer) {
window.viewer.destroy();
window.viewer = null;
}
});
onMounted(() => {
nextTick(() => {
initMap();
});
});
// Initialize the main Cesium 3D map
const initMap = () => {
// Set Cesium Ion token
Cesium.Ion.defaultAccessToken = token;
// Set default view range (China region)
Cesium.Camera.DEFAULT_VIEW_RECTANGLE = Cesium.Rectangle.fromDegrees(89.5, 20.4, 110.4, 61.2);
// Instantiate the main map Viewer
window.viewer = new Cesium.Viewer('earth', {
animation: false, // Time animation widget
timeline: false, // Timeline
infoBox: false, // Click feature popup
geocoder: false, // Search box
homeButton: false, // Reset view button
sceneModePicker: false, // 2D/3D switch button
baseLayerPicker: false, // Base map switch panel
navigationHelpButton: false, // Operation help popup
fullscreenButton: false, // Fullscreen button
selectionIndicator: false, // Selected feature highlight box
shouldAnimate: false // Turn off automatic animation rendering to save performance
});
// Initialize imagery base map: imagery layer + road text annotation layer overlay
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)) => {
// Read polygon coordinates from GeoJSON
let holeList = dataHoleList.features[0].geometry.coordinates;
// Recursively flatten multi-dimensional coordinate array
let holes = dealArr(holeList);
// Convert longitude/latitude array to Cartesian coordinates
holes = Cesium.Cartesian3.fromDegreesArray(holes);
// Directly draw the GeoJSON data area surface, fill mask
let districtMask = {
id: 'districtMask',
name: 'Administrative District Internal Mask',
polygon: {
hierarchy: {
positions: holes
},
material: maskColor,
fill: true,
clampToGround: true // Ground-clamped
}
};
window.viewer.entities.add(districtMask);
// Draw the boundary outline of the GeoJSON data area
let maskLine = {
polyline: {
positions: holes,
width: 5,
material: dataBorderColor,
clampToGround: true // Ground-clamped rendering
}
};
window.viewer.entities.add(maskLine);
};
// Method to switch base map type: vector base map <==> imagery base map (imagery overlaid with road annotations)
const changeMap = () => {
// Guard: exit directly if map instance does not exist
if (!window.viewer) {
return;
}
// Clear all current imagery layers
window.viewer.imageryLayers.removeAll();
if (!isImagery.value) {
// Switch to imagery base map: imagery layer + road text annotation layer overlay
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 {
// Switch back to vector base map
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);
}
// Toggle state flag
isImagery.value = !isImagery.value;
};
// Method to recursively flatten multi-dimensional coordinate arrays
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
}
// Method to fly the camera to a preset initial view
const flyTo = () => {
window.viewer.camera.flyTo({
destination: Cesium.Cartesian3.fromDegrees(121.21709895255839, 31.01351793218674, 66403.24207453332),
orientation: {
heading: Cesium.Math.toRadians(359.8090548395541), // Heading angle
pitch: Cesium.Math.toRadians(-89.63236031882052), // Pitch angle
roll: Cesium.Math.toRadians(0) // Roll angle
},
duration: 3 // Flight duration 3 seconds
});
};
</script>
3. CSS style code:
.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;
}
Why not use holes to create the reverse effect?
Cesium's PolygonHierarchy.holes mechanism has fixed logic: the outer coordinates are the solid surface, and the holes array is always the cutout area. There is no "reverse hollow" parameter. To darken the inside of an area, directly drawing the GeoJSON data area surface is the only elegant, bug-free solution.
Summary:
This article, based on the previous "Cesium Implements Reverse Masking and Hollowing for Administrative Districts," completes the advanced modification of drawing an internal mask for the GeoJSON data area, rounding out the two mainstream masking solutions for Cesium administrative district visualization.
The Amap tile resources used in the case study of this article are only for learning and technical research demonstrations, not for online commercial deployment scenarios. If an enterprise project officially goes live using similar map tile resources, please go to the corresponding map service provider platform to complete qualification certification and apply for compliant invocation keys.
Top 1 of 2 from juejin.cn, machine-translated. The original thread is authoritative.
666
[Grin]