runtime-core.esm-bundler.js:268 Uncaught TypeError: Cesium.UrlTemplateTerrainProvider is not a constructor
at loadMapService (CesiumViewer.vue:163:30)
at switchMapType (CesiumViewer.vue:135:5)
at callWithErrorHandling (runtime-core.esm-bundler.js:199:19)
at callWithAsyncErrorHandling (runtime-core.esm-bundler.js:206:17)
at HTMLSelectElement.invoker (runtime-dom.esm-bundler.js:729:5) 如上所示报错,还是没有加载三维地形图,修改下面代码:<template>
<div class="monitor-container">
<div class="top-bar">
<div class="left-section">
<div class="weather-module">
<i class="fas fa-sun weather-icon"></i>
<span>晴 31℃/西北风</span>
</div>
</div>
<div class="center-section">
<div class="platform-title">
<div class="trapezoid-bg">
<h1>株洲市"天空地水"动态监测平台</h1>
</div>
</div>
</div>
<div class="right-section">
<div class="time-module">
{{ currentTime }} {{ currentWeek }}
</div>
<div class="controls-group">
<div class="projection-switcher">
<select v-model="projectionType" @change="switchProjectionType">
<option value="c">经纬度投影</option>
<option value="w">球面墨卡托投影</option>
</select>
</div>
<div class="map-switcher">
<select v-model="selectedMapType" @change="switchMapType">
<option v-for="option in mapOptions" :value="option.value" :key="option.value">
{{ option.label }}
</option>
</select>
</div>
<div class="network-switcher">
<select v-model="networkType" @change="switchNetworkType">
<option value="external">外网地图</option>
<option value="internal">内网地图</option>
</select>
</div>
</div>
</div>
</div>
<div id="cesiumContainer"></div>
<div v-if="terrainEnabled" class="terrain-badge">
<i class="fas fa-mountain"></i>
<span>三维地形模式</span>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue';
// 天地图服务密钥
const TIANDITU_TOKEN = '72d487f15710ca558987b9baaba13736';
// 当前时间和星期
const currentTime = ref("");
const currentWeek = ref("");
const weekMap = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'];
const updateTime = () => {
const now = new Date();
currentTime.value = now.toLocaleString('zh-CN', {
year: 'numeric', month: '2-digit', day: '2-digit',
hour: '2-digit', minute: '2-digit', second: '2-digit',
hour12: false
}).replace(/\//g, '-').replace(/(\d{4})-(\d{2})-(\d{2})/, '$ 1年$ 2月$ 3日');
currentWeek.value = weekMap[now.getDay()];
};
// 网络类型选择
const networkType = ref('external');
// 地图类型选择
const selectedMapType = ref('img_c');
// 投影类型选择
const projectionType = ref('c');
// 三维地形启用状态
const terrainEnabled = ref(false);
// 地图选项配置
const mapOptions = ref([
{ value: 'img_c', label: '影像+注记' },
{ value: 'vec_c', label: '矢量+注记' },
{ value: 'terrain', label: '三维地形图' }
]);
// Cesium相关变量
let viewer = null;
let tiandituLayers = { baseLayer: null, annotationLayer: null };
let timeInterval = null;
// 石河子范围矩形
const shiheziRectangle = ref(null);
// 网络类型切换
const switchNetworkType = () => {
if (networkType.value === 'internal') {
mapOptions.value = [
{ value: 'img', label: '内网影像地图' },
{ value: 'vec', label: '内网矢量地图' }
];
selectedMapType.value = 'img';
terrainEnabled.value = false;
} else {
mapOptions.value = [
{ value: 'img_c', label: '影像+注记' },
{ value: 'vec_c', label: '矢量+注记' },
{ value: 'terrain', label: '三维地形图' }
];
selectedMapType.value = 'img_c';
}
loadMapService(selectedMapType.value);
loadShiheziVector();
setTimeout(flyToShihezi, 500);
};
// 地图类型切换
const switchMapType = () => {
if (viewer) {
terrainEnabled.value = selectedMapType.value === 'terrain';
loadMapService(selectedMapType.value);
}
};
// 投影类型切换
const switchProjectionType = () => {
if (viewer) {
loadMapService(selectedMapType.value);
setTimeout(flyToShihezi, 500);
}
};
// 加载地图服务
const loadMapService = (type) => {
if (!viewer || !viewer.imageryLayers) return;
// 清除现有图层
if (tiandituLayers.baseLayer) viewer.imageryLayers.remove(tiandituLayers.baseLayer);
if (tiandituLayers.annotationLayer) viewer.imageryLayers.remove(tiandituLayers.annotationLayer);
// 处理三维地形模式
if (type === 'terrain') {
// 设置三维地形
if (!(viewer.scene.terrainProvider instanceof Cesium.CesiumTerrainProvider)) {
viewer.terrainProvider = new Cesium.EllipsoidTerrainProvider();
}
// 修复:使用正确的 UrlTemplateTerrainProvider
viewer.terrainProvider = new Cesium.UrlTemplateTerrainProvider({
url: `https://t{s}.tianditu.gov.cn/mapservice/swdx?tk=${TIANDITU_TOKEN}`,
subdomains: ['0','1','2','3','4','5','6','7']
});
// 添加地形注记层
const annoUrl = projectionType.value === 'c'
? `https://t{s}.tianditu.gov.cn/cta/wmts?tk=${TIANDITU_TOKEN}`
: `https://t{s}.tianditu.gov.cn/cta_w/wmts?tk=${TIANDITU_TOKEN}`;
tiandituLayers.baseLayer = viewer.imageryLayers.addImageryProvider(
new Cesium.WebMapTileServiceImageryProvider({
url: annoUrl,
layer: projectionType.value === 'c' ? 'cta' : 'cta_w',
style: "default",
tileMatrixSetID: projectionType.value,
format: "tiles",
subdomains: ['0','1','2','3','4','5','6','7'],
maximumLevel: 18,
credit: new Cesium.Credit("天地图地形注记")
})
);
} else {
// 确保不使用三维地形
viewer.terrainProvider = new Cesium.EllipsoidTerrainProvider();
if (networkType.value === 'internal') {
// 内网地图服务
const baseUrl = "http://59.255.48.160:81";
const projectionSuffix = projectionType.value === 'c' ? '_c' : '_w';
if (type === 'img') {
// 内网影像地图
tiandituLayers.baseLayer = viewer.imageryLayers.addImageryProvider(
new Cesium.WebMapTileServiceImageryProvider({
url: `${baseUrl}/img${projectionSuffix}/wmts`,
layer: "img",
style: "default",
tileMatrixSetID: projectionType.value,
format: "tiles",
credit: new Cesium.Credit("内网影像地图"),
maximumLevel: 18
})
);
} else if (type === 'vec') {
// 内网矢量地图
tiandituLayers.baseLayer = viewer.imageryLayers.addImageryProvider(
new Cesium.WebMapTileServiceImageryProvider({
url: `${baseUrl}/vec${projectionSuffix}/wmts`,
layer: "vec",
style: "default",
tileMatrixSetID: projectionType.value,
format: "tiles",
credit: new Cesium.Credit("内网矢量地图"),
maximumLevel: 18
})
);
}
} else {
// 外网天地图服务
const baseUrlTemplate = `https://t{s}.tianditu.gov.cn/{layer}_${projectionType.value}/wmts?tk=${TIANDITU_TOKEN}`;
const layerConfigs = {
img_c: {
baseLayer: { layer: "img", credit: "天地图影像" },
annotationLayer: { layer: projectionType.value === 'c' ? "cia" : "cia_w", credit: "天地图注记" }
},
vec_c: {
baseLayer: { layer: "vec", credit: "天地图矢量" },
annotationLayer: { layer: projectionType.value === 'c' ? "cva" : "cva_w", credit: "天地图注记" }
}
};
const config = layerConfigs[type];
if (!config) return;
// 添加基础图层
tiandituLayers.baseLayer = viewer.imageryLayers.addImageryProvider(
new Cesium.WebMapTileServiceImageryProvider({
url: baseUrlTemplate.replace('{layer}', config.baseLayer.layer),
layer: config.baseLayer.layer,
style: "default",
tileMatrixSetID: projectionType.value,
format: "tiles",
credit: new Cesium.Credit(config.baseLayer.credit),
subdomains: ['0','1','2','3','4','5','6','7'],
maximumLevel: 18
})
);
// 添加注记图层
tiandituLayers.annotationLayer = viewer.imageryLayers.addImageryProvider(
new Cesium.WebMapTileServiceImageryProvider({
url: baseUrlTemplate.replace('{layer}', config.annotationLayer.layer),
layer: config.annotationLayer.layer,
style: "default",
tileMatrixSetID: projectionType.value,
format: "tiles",
credit: new Cesium.Credit(config.annotationLayer.credit),
subdomains: ['0','1','2','3','4','5','6','7'],
maximumLevel: 18
})
);
}
}
};
// 加载石河子矢量数据
const loadShiheziVector = async () => {
if (!viewer) return;
try {
// 清除现有石河子数据
const dataSources = viewer.dataSources;
for (let i = dataSources.length - 1; i >= 0; i--) {
if (dataSources.get(i).name === 'shihezi') {
dataSources.remove(dataSources.get(i));
}
}
// 加载石河子市整体边界
const cityDataSource = await Cesium.GeoJsonDataSource.load(
"https://geo.datav.aliyun.com/areas_v3/bound/geojson?code=659001", {
clampToGround: true,
stroke: Cesium.Color.GREEN.withAlpha(0.8),
strokeWidth: 2,
fill: Cesium.Color.WHITE.withAlpha(0.3),
name: 'shihezi'
}
);
viewer.dataSources.add(cityDataSource);
// 添加石河子市标签
cityDataSource.entities.values.forEach(entity => {
if (entity.polygon && entity.properties && entity.properties.name) {
const hierarchy = entity.polygon.hierarchy.getValue(Cesium.JulianDate.now());
const positions = hierarchy.positions;
const center = Cesium.BoundingSphere.fromPoints(positions).center;
entity.label = {
text: "石河子市",
font: '18px 黑体',
fillColor: Cesium.Color.WHITE,
outlineColor: Cesium.Color.BLACK,
outlineWidth: 3,
style: Cesium.LabelStyle.FILL_AND_OUTLINE,
verticalOrigin: Cesium.VerticalOrigin.CENTER,
horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
pixelOffset: new Cesium.Cartesian2(0, 0),
disableDepthTestDistance: Number.POSITIVE_INFINITY,
scaleByDistance: new Cesium.NearFarScalar(1e3, 1.0, 1e6, 0.5)
};
entity.position = center;
}
});
// 保存石河子范围
const positions = cityDataSource.entities.values[0].polygon.hierarchy.getValue().positions;
const cartographicPositions = positions.map(pos => Cesium.Cartographic.fromCartesian(pos));
const minLon = Math.min(...cartographicPositions.map(p => p.longitude));
const maxLon = Math.max(...cartographicPositions.map(p => p.longitude));
const minLat = Math.min(...cartographicPositions.map(p => p.latitude));
const maxLat = Math.max(...cartographicPositions.map(p => p.latitude));
shiheziRectangle.value = new Cesium.Rectangle(minLon, minLat, maxLon, maxLat);
} catch (error) {
console.error("加载石河子数据失败:", error);
}
};
// 定位到石河子
const flyToShihezi = () => {
if (viewer && shiheziRectangle.value) {
// 根据当前投影类型转换坐标
let destination;
if (projectionType.value === 'c') {
destination = shiheziRectangle.value;
} else {
const sw = Cesium.WebMercatorProjection.geodeticToWebMercator(
new Cesium.Cartographic(shiheziRectangle.value.west, shiheziRectangle.value.south)
);
const ne = Cesium.WebMercatorProjection.geodeticToWebMercator(
new Cesium.Cartographic(shiheziRectangle.value.east, shiheziRectangle.value.north)
);
destination = new Cesium.Rectangle(sw.x, sw.y, ne.x, ne.y);
}
viewer.camera.flyTo({
destination: destination,
orientation: {
heading: Cesium.Math.toRadians(0),
pitch: Cesium.Math.toRadians(-70),
roll: 0
},
duration: 1
});
}
};
onMounted(async () => {
try {
// 设置Cesium Ion访问令牌
Cesium.Ion.defaultAccessToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI0ZTdiZDBhZS0xNzBhLTRjZGUtOTY4NC1kYzA5ZDEyNGEyNjUiLCJpZCI6MzE2OTI5LCJpYXQiOjE3NTEzMzg2Mzh9.9QHGIwaWkUOX0NOSre5369rrf1k6bGhZu7xUQia4JmE';
// 初始化时间更新
timeInterval = setInterval(updateTime, 1000);
updateTime();
// 初始化Cesium Viewer
viewer = new Cesium.Viewer('cesiumContainer', {
baseLayerPicker: false,
timeline: false,
animation: false,
geocoder: false,
sceneModePicker: false,
navigationHelpButton: false,
homeButton: false,
selectionIndicator: false,
infoBox: false,
navigationInstructionsInitiallyVisible: false,
scene3DOnly: true,
terrainProvider: new Cesium.EllipsoidTerrainProvider(),
imageryProvider: new Cesium.WebMapTileServiceImageryProvider({
url: `https://t{s}.tianditu.gov.cn/img_${projectionType.value}/wmts?tk=${TIANDITU_TOKEN}`,
layer: "img",
style: "default",
tileMatrixSetID: projectionType.value,
subdomains: ['0','1','2','3','4','5','6','7'],
maximumLevel: 18
})
});
// 强制设置中国视角
viewer.camera.setView({
destination: Cesium.Rectangle.fromDegrees(73.0, 3.0, 136.0, 59.0)
});
// 加载石河子数据
await loadShiheziVector();
flyToShihezi();
// 加载地图服务
loadMapService(selectedMapType.value);
} catch (error) {
console.error("初始化失败:", error);
}
});
onBeforeUnmount(() => {
// 清除定时器
if (timeInterval) clearInterval(timeInterval);
// 销毁Cesium Viewer
if (viewer) {
viewer.destroy();
viewer = null;
}
});
</script>
<style scoped>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: "Microsoft YaHei", sans-serif;
}
.monitor-container {
width: 100vw;
height: 100vh;
background: radial-gradient(circle at center, #0c2a50 0%, #0a1a35 100%);
overflow: hidden;
position: fixed;
top: 0;
left: 0;
display: flex;
flex-direction: column;
}
.top-bar {
height: 70px;
background: linear-gradient(90deg, #0a1a35 0%, #1a3a6e 50%, #0a1a35 100%);
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 15px;
position: relative;
z-index: 100;
border-bottom: 1px solid rgba(0, 150, 255, 0.3);
}
.left-section {
display: flex;
align-items: center;
flex: 0 0 auto;
}
.center-section {
flex: 1 1 auto;
display: flex;
justify-content: center;
}
.right-section {
display: flex;
align-items: center;
flex: 0 0 auto;
gap: 10px;
}
.controls-group {
display: flex;
gap: 10px;
}
.weather-module {
display: flex;
align-items: center;
color: white;
font-size: 14px;
min-width: 120px;
justify-content: center;
}
.weather-icon {
color: #ffcc00;
margin-right: 8px;
font-size: 16px;
}
.platform-title {
position: relative;
z-index: 10;
max-width: 500px;
}
.trapezoid-bg {
position: relative;
padding: 0 40px;
height: 70px;
display: flex;
align-items: center;
}
.trapezoid-bg::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 60, 113, 0.6);
clip-path: polygon(0% 0%, 100% 0%, 90% 100%, 10% 100%);
z-index: -1;
}
.platform-title h1 {
font-size: 24px;
font-weight: bold;
background: linear-gradient(to bottom, #a2e7eb, #00f2fe);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
text-shadow: 0 0 8px rgba(0, 242, 254, 0.3);
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
max-width: 100%;
}
.time-module {
color: white;
font-size: 14px;
min-width: 220px;
text-align: center;
white-space: nowrap;
}
.network-switcher select,
.map-switcher select,
.projection-switcher select {
background: rgba(10, 26, 53, 0.8);
color: #66ffff;
border: 1px solid rgba(0, 150, 255, 0.5);
border-radius: 4px;
padding: 6px 12px;
font-size: 14px;
outline: none;
cursor: pointer;
box-shadow: 0 0 8px rgba(0, 150, 255, 0.3);
transition: all 0.3s ease;
min-width: 120px;
}
.network-switcher select:hover,
.map-switcher select:hover,
.projection-switcher select:hover {
background: rgba(26, 58, 110, 0.8);
border-color: #00f2fe;
box-shadow: 0 0 12px rgba(0, 242, 254, 0.5);
}
.network-switcher select:focus,
.map-switcher select:focus,
.projection-switcher select:focus {
border-color: #00f2fe;
}
#cesiumContainer {
width: 100%;
height: calc(100vh - 70px);
background-color: #000;
position: relative;
}
.terrain-badge {
position: absolute;
top: 80px;
right: 20px;
padding: 6px 12px;
background: rgba(0, 150, 255, 0.3);
color: #66ffff;
border-radius: 4px;
font-size: 12px;
display: flex;
align-items: center;
gap: 6px;
z-index: 100;
backdrop-filter: blur(5px);
}
@media (max-width: 1600px) {
.platform-title h1 { font-size: 20px; }
.weather-module, .time-module { font-size: 13px; }
.network-switcher select, .map-switcher select, .projection-switcher select {
padding: 5px 10px; font-size: 13px; min-width: 110px;
}
}
@media (max-width: 1400px) {
.platform-title h1 { font-size: 18px; }
.trapezoid-bg { padding: 0 30px; }
.time-module { min-width: 180px; }
.controls-group { gap: 8px; }
}
@media (max-width: 1200px) {
.platform-title h1 { font-size: 16px; }
.trapezoid-bg { padding: 0 20px; }
.time-module { min-width: 160px; font-size: 12px; }
.network-switcher select, .map-switcher select, .projection-switcher select {
padding: 4px 8px; font-size: 12px; min-width: 100px;
}
}
@media (max-width: 992px) {
.time-module { display: none; }
.platform-title h1 { font-size: 14px; }
.trapezoid-bg { padding: 0 15px; }
.weather-module { min-width: auto; }
.network-switcher select, .map-switcher select, .projection-switcher select {
padding: 4px 6px; font-size: 11px; min-width: 90px;
}
}
@media (max-width: 768px) {
.weather-module { display: none; }
.platform-title h1 { font-size: 12px; }
.trapezoid-bg { padding: 0 10px; }
.controls-group { gap: 5px; }
.network-switcher select, .map-switcher select, .projection-switcher select {
min-width: 80px;
}
}
</style>