1. vite 项目构建

1.1 初始化 vite 项目(根据提示和自己需求选择对应功能)

1
2
3
npm create vue3@latest
# 或者yarn
yarn build vue3@latest

1.2 安装cesium@1.99、vite-plugin-cesium并配置

1
2
3
npm install cesium@1.99 vite-plugin-cesium
# 或者yarn
yarn add cesium@1.99 vite-plugin-cesium
  • vite.config.js 文件引入 cesium 插件,添加如下代码:
1
2
3
4
5
6
7
8
9
10
11
12
import { fileURLToPath, URL } from "node:url";
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import cesium from "vite-plugin-cesium"; //添加Cesium插件
export default defineConfig({
plugins: [vue(), cesium()],
resolve: {
alias: {
"@": fileURLToPath(new URL("./src", import.meta.url)),
},
},
});

1.3 安装elementPlus

  • 下载elementPlus安装包
1
2
3
npm install element-plus --save
# 或者yarn
yarn add element-plus
  • 在 main.js 中引入elementPlus
1
2
3
4
5
6
7
import ElementPlus from "element-plus";
import "element-plus/dist/index.css";
import { createApp } from "vue";
import App from "./App.vue";
const app = createApp(App);
app.use(ElementPlus);
app.mount("#app");
  • 自动导入时,需要安装unplugin-vue-componentsunplugin-auto-import这两款插件
1
2
# 下载插件
npm install -D unplugin-vue-components unplugin-auto-import
  • 然后在vite.config.js 引入elementPlus插件,添加如下代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//自动引入element插件
import AutoImport from "unplugin-auto-import/vite";
import Components from "unplugin-vue-components/vite";
import { ElementPlusResolver } from "unplugin-vue-components/resolvers";
export default defineConfig({
plugins: [
vue(),
cesium(),
AutoImport({
resolvers: [ElementPlusResolver()],
}),
Components({
resolvers: [ElementPlusResolver()],
}),
],
resolve: {
alias: {
"@": fileURLToPath(new URL("./src", import.meta.url)),
},
},
});
  • 需要使用 elementPlue 的 Icon 图标,需要安装@element-plus/icons-vue
1
2
3
4
# NPM
$ npm install @element-plus/icons-vue
# Yarn
$ yarn add @element-plus/icons-vue
  • 在 main.ts 添加如下代码全局注入即可
1
2
3
4
5
import * as ElementPlusIconsVue from "@element-plus/icons-vue";
const app = createApp(App);
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component);
}

2. 初始化 viewer 地球

2.1 删除默认的 vite 项目组件,新建 viewer.vue 组件,并在 App.vue 中使用

  • viewer 组件初始化,注意申请替换自己的 tocken
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<template>
<div id="cesiumContainer"></div>
</template>
<script setup>
import { onMounted } from "vue";
import * as Cesium from "cesium";
// Cesium.Ion.defaultAccessToken = '你的cesiumtoken'
onMounted(() => {
const viewer = new Cesium.Viewer("cesiumContainer", {
animation: false, //是否显示动画控件
baseLayerPicker: false, //是否显示图层选择控件
geocoder: false, //是否显示地名查找控件
timeline: false, //是否显示时间线控件
sceneModePicker: false, //是否显示投影方式控件
navigationHelpButton: false, //是否显示帮助信息控件
fullscreenButton: false, //是否显示全屏按钮
infoBox: false, //是否显示点击要素之后显示的信息
homeButton: false, //是否显示Home按钮
imageryProvider: new Cesium.ArcGisMapServerImageryProvider({
url: "https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer",//使用arcgis在线底图替换默认的bingmap底图
}),
});
viewer.cesiumWidget.creditContainer.style.display = "none"; //移除版权信息
});
<style scoped>
#cesiumContainer {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
</style>
  • 在 App.vue 上引入组件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<script setup>
import viewer from "./components/viewer.vue";
</script>
<template>
<viewer></viewer>
</template>
<style>
html,
body,
#app {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
</style>
  • 运行项目即可看到地球的加载,干净整洁

viewer视图

3. 加载 gltf/glb 模型并定位

  • 在 viewer.vue 组件中,添加加载 glb 模型的方法,需要准备 glb 模型,放在 public 的 model 文件夹下,加载模型的核心代码如下:
1
2
3
4
5
6
7
8
9
10
11
//模型的url,此处放在publid文件夹下
let modelUrl = "/model/TongLiChe.glb";
const model = viewer.scene.primitives.add(
Cesium.Model.fromGltf({
url: modelUrl,
show: true,
scale: 0.001,
maximumScale: 1,
loader: "url-loader",
})
);
  • 加载模型之后,设置模型中心点的初始坐标(参数),并且视图缩放到模型中,核心代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
import { ref } from "vue";
//模型的url,此处放在publid文件夹下
let modelUrl = "/model/TongLiChe.glb";
const model = viewer.scene.primitives.add(
Cesium.Model.fromGltf({
url: modelUrl,
show: true,
scale: 0.001,
maximumScale: 1,
loader: "url-loader",
})
);
//模型参数配置,后续动态改变参数,设置响应式
const params = ref({
tx: 115.988114, //模型中心X轴坐标(经度,单位:十进制度)
ty: 28.694716, //模型中心Y轴坐标(纬度,单位:十进制度)
tz: 0, //模型中心Z轴坐标(高程,单位:米)
rx: 0, //X轴(经度)方向旋转角度(单位:度)
ry: 0, //Y轴(纬度)方向旋转角度(单位:度)
rz: -90, //Z轴(高程)方向旋转角度(单位:度)
sc: 1, //比例系数
});
//模型根据参数变化到对应位置。
update3dtilesMaxtrix(model, params);
model.readyPromise.then(function (model) {
//定位到模型的位置,此处使用viewer.zoomTo(model)报错
viewer.camera.setView({
destination: Cesium.Cartesian3.fromDegrees(
params.value.tx,
params.value.ty,
150.0
),
orientation: {
heading: Cesium.Math.toRadians(0),
pitch: Cesium.Math.toRadians(-90),
roll: Cesium.Math.toRadians(0),
},
});
});
// 模型旋转矩阵变换
function update3dtilesMaxtrix(model, params, Cesium) {
//旋转
let mx = Cesium.Matrix3.fromRotationX(Cesium.Math.toRadians(params.value.rx));
let my = Cesium.Matrix3.fromRotationY(Cesium.Math.toRadians(params.value.ry));
let mz = Cesium.Matrix3.fromRotationZ(Cesium.Math.toRadians(params.value.rz));
// let mx = Cesium.Matrix3.fromRotationX(Cesium.Math.toRadians(0))
// let my = Cesium.Matrix3.fromRotationY(Cesium.Math.toRadians(0))
// let mz = Cesium.Matrix3.fromRotationZ(Cesium.Math.toRadians(20))
let rotationX = Cesium.Matrix4.fromRotationTranslation(mx);
let rotationY = Cesium.Matrix4.fromRotationTranslation(my);
let rotationZ = Cesium.Matrix4.fromRotationTranslation(mz);
//平移
let position = Cesium.Cartesian3.fromDegrees(
params.value.tx,
params.value.ty,
params.value.tz
);
let m = Cesium.Transforms.eastNorthUpToFixedFrame(position);
//旋转、平移矩阵相乘
Cesium.Matrix4.multiply(m, rotationX, m);
Cesium.Matrix4.multiply(m, rotationY, m);
Cesium.Matrix4.multiply(m, rotationZ, m);
//赋值给model
if (Cesium.defined(model.primitive)) {
model.primitive.modelMatrix = m;
}
model.modelMatrix = m;
model.color = Cesium.Color.WHITE.withAlpha(1);
}
  • 加载模型之后,看到如下模型和成果:

glb模型加载

4. 动态调整模型参数

4.1 设置弹窗数据与样式

  • 定义 params 变量用于存储模型经纬度、高程、X 轴旋转、Y 轴旋转、Z 轴旋转的参数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
<el-card v-if="glfset" style="position: absolute; top: 20px; right: 20px">
<div class="text-h6">
GLTF/GLB模型修正
<el-icon @click="glfset = false" style="position: absolute; right: 15px"
><Close
/></el-icon>
</div>
<div class="inputItem">
<div>
<el-text class="mx-1">经度:</el-text>
<el-input-number v-model="params.tx" step="0.000005" />
</div>
<div>
<el-text class="mx-1">纬度:</el-text>
<el-input-number v-model="params.ty" step="0.000005" />
</div>
<div>
<el-text class="mx-1">高程:</el-text>
<el-input-number v-model="params.tz" step="1" />
</div>
<div>
<el-text class="mx-1">X轴:</el-text>
<el-input-number v-model="params.rx" step="1" />
</div>
<div>
<el-text class="mx-1">Y轴:</el-text>
<el-input-number v-model="params.ry" step="1" />
</div>
<div>
<el-text class="mx-1">Z轴:</el-text>
<el-input-number v-model="params.rz" step="1" />
</div>
</div>
</el-card>
<script>
//模型参数配置
const params = ref({
tx: 115.988114, //模型中心X轴坐标(经度,单位:十进制度)
ty: 28.694716, //模型中心Y轴坐标(纬度,单位:十进制度)
tz: 0, //模型中心Z轴坐标(高程,单位:米)
rx: 0, //X轴(经度)方向旋转角度(单位:度)
ry: 0, //Y轴(纬度)方向旋转角度(单位:度)
rz: -90, //Z轴(高程)方向旋转角度(单位:度)
});
</script>
//样式如下
<style scoped>
.inputItem {
display: flex;
width: 220px;
flex-direction: column;
height: 300px;
/* justify-items: center; */
justify-content: space-around;
align-items: space-around;
padding: 10px 3px;
}
.el-input-number {
width: 160px !important;
}
</style>
  • 其效果如图所示:

属性弹窗

4.2 动态调整模型位置与旋转角度

  • 根据绑定的params,设置点击模型弹窗属性设置弹窗,然后监听params参数的改变,通过自定义的update3dtilesMaxtrix函数,改变模型的位置与姿态,核心update3dtilesMaxtrix函数代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
let currentGlfModel; //储存模型变量
//监听移动参数
watch(
() => params,
(newVal) => {
if (newVal) {
console.log(newVal);
if (!!currentGlfModel) {
update3dtilesMaxtrix(currentGlfModel, newVal, Cesium);
}
}
},
{ deep: true }
);
function update3dtilesMaxtrix(model, params, Cesium) {
//旋转
let mx = Cesium.Matrix3.fromRotationX(Cesium.Math.toRadians(params.value.rx));
let my = Cesium.Matrix3.fromRotationY(Cesium.Math.toRadians(params.value.ry));
let mz = Cesium.Matrix3.fromRotationZ(Cesium.Math.toRadians(params.value.rz));
let rotationX = Cesium.Matrix4.fromRotationTranslation(mx);
let rotationY = Cesium.Matrix4.fromRotationTranslation(my);
let rotationZ = Cesium.Matrix4.fromRotationTranslation(mz);
//平移
let position = Cesium.Cartesian3.fromDegrees(
params.value.tx,
params.value.ty,
params.value.tz
);
let m = Cesium.Transforms.eastNorthUpToFixedFrame(position);
//旋转、平移矩阵相乘
Cesium.Matrix4.multiply(m, rotationX, m);
Cesium.Matrix4.multiply(m, rotationY, m);
Cesium.Matrix4.multiply(m, rotationZ, m);
//赋值给model
if (Cesium.defined(model.primitive)) {
model.primitive.modelMatrix = m;
}
model.modelMatrix = m;
}
// 点击模型弹出设置参数
const handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
handler.setInputAction(function (movement) {
let pickedObject = viewer.scene.pick(movement.position);
console.log("点击实体", pickedObject);
currentGlfModel = pickedObject;
if (Cesium.defined(pickedObject)) {
if (pickedObject.primitive.isCesium3DTileset == undefined) {
//打开弹窗
glfset.value = true;
}
}
}, Cesium.ScreenSpaceEventType.LEFT_DOWN);

4.3 整体的效果

  • 整体操作的效果如图所示(需耐心加载哦):

glf控制效果

如需进一步查看更多内容,访问cylgis.top 主页,查看本教程的完整代码并获取demo。

4.4 完整代码

  • viewer组件的完整代码如下,需要自备glb/gltf模型,并更改模型加载路径,并使用自己的Cesium token,此功能的完整demo可联系作者免费获取。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
<template>
<div id="cesiumContainer"></div>
<el-card v-if="glfset" style="position: absolute; top: 20px; right: 20px">
<div class="text-h6">
GLTF/GLB模型修正
<el-icon @click="glfset = false" style="position: absolute; right: 15px"
><Close
/></el-icon>
</div>
<div class="inputItem">
<div>
<el-text class="mx-1">经度:</el-text>
<el-input-number v-model="params.tx" step="0.000005" />
</div>
<div>
<el-text class="mx-1">纬度:</el-text>
<el-input-number v-model="params.ty" step="0.000005" />
</div>
<div>
<el-text class="mx-1">高程:</el-text>
<el-input-number v-model="params.tz" step="1" />
</div>
<div>
<el-text class="mx-1">X轴:</el-text>
<el-input-number v-model="params.rx" step="1" />
</div>
<div>
<el-text class="mx-1">Y轴:</el-text>
<el-input-number v-model="params.ry" step="1" />
</div>
<div>
<el-text class="mx-1">Z轴:</el-text>
<el-input-number v-model="params.rz" step="1" />
</div>
</div>
</el-card>
</template>

<script setup>
import { ref, onMounted, watch } from "vue";
import * as Cesium from "cesium";
// Cesium.Ion.defaultAccessToken = '你的cesiumtoken'

const glfset = ref(false);

// 模型参数配置
const params = ref({
tx: 115.988114, //模型中心X轴坐标(经度,单位:十进制度)
ty: 28.694716, //模型中心Y轴坐标(纬度,单位:十进制度)
tz: 0, //模型中心Z轴坐标(高程,单位:米)
rx: 0, //X轴(经度)方向旋转角度(单位:度)
ry: 0, //Y轴(纬度)方向旋转角度(单位:度)
rz: -90, //Z轴(高程)方向旋转角度(单位:度)
});

let currentGlfModel; //储存模型变量
//监听移动参数
watch(
() => params,
(newVal) => {
if (newVal) {
console.log(newVal);
if (!!currentGlfModel) {
update3dtilesMaxtrix(currentGlfModel, newVal, Cesium);
}
}
},
{ deep: true }
);

function update3dtilesMaxtrix(model, params, Cesium) {
//旋转
let mx = Cesium.Matrix3.fromRotationX(Cesium.Math.toRadians(params.value.rx));
let my = Cesium.Matrix3.fromRotationY(Cesium.Math.toRadians(params.value.ry));
let mz = Cesium.Matrix3.fromRotationZ(Cesium.Math.toRadians(params.value.rz));
let rotationX = Cesium.Matrix4.fromRotationTranslation(mx);
let rotationY = Cesium.Matrix4.fromRotationTranslation(my);
let rotationZ = Cesium.Matrix4.fromRotationTranslation(mz);
//平移
let position = Cesium.Cartesian3.fromDegrees(
params.value.tx,
params.value.ty,
params.value.tz
);
let m = Cesium.Transforms.eastNorthUpToFixedFrame(position);
//旋转、平移矩阵相乘
Cesium.Matrix4.multiply(m, rotationX, m);
Cesium.Matrix4.multiply(m, rotationY, m);
Cesium.Matrix4.multiply(m, rotationZ, m);
//赋值给model
if (Cesium.defined(model.primitive)) {
model.primitive.modelMatrix = m;
}
model.modelMatrix = m;
}

onMounted(() => {
const viewer = new Cesium.Viewer("cesiumContainer", {
animation: false, //是否显示动画控件
baseLayerPicker: false, //是否显示图层选择控件
geocoder: false, //是否显示地名查找控件
timeline: false, //是否显示时间线控件
sceneModePicker: false, //是否显示投影方式控件
navigationHelpButton: false, //是否显示帮助信息控件
fullscreenButton: false, //是否显示全屏按钮
infoBox: false, //是否显示点击要素之后显示的信息
homeButton: false, //是否显示Home按钮
imageryProvider: new Cesium.ArcGisMapServerImageryProvider({
url: "https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer",
}),
});
viewer.cesiumWidget.creditContainer.style.display = "none"; //移除版权信息
//模型的url,此处放在publid文件夹下
let modelUrl = "/model/TongLiChe.glb";
currentGlfModel = viewer.scene.primitives.add(
Cesium.Model.fromGltf({
url: modelUrl,
show: true,
scale: 0.001,
maximumScale: 1,
loader: "url-loader",
})
);

//模型根据参数变化到对应位置。
update3dtilesMaxtrix(currentGlfModel, params, Cesium);

currentGlfModel.readyPromise.then(function (model) {
//定位到模型的位置,此处使用viewer.zoomTo(model)报错
viewer.camera.setView({
destination: Cesium.Cartesian3.fromDegrees(
params.value.tx,
params.value.ty,
150.0
),
orientation: {
heading: Cesium.Math.toRadians(0),
pitch: Cesium.Math.toRadians(-90),
roll: Cesium.Math.toRadians(0),
},
});
});

// 点击模型弹出设置参数
const handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
handler.setInputAction(function (movement) {
let pickedObject = viewer.scene.pick(movement.position);
console.log("点击实体", pickedObject);
currentGlfModel = pickedObject;
if (Cesium.defined(pickedObject)) {
if (pickedObject.primitive.isCesium3DTileset == undefined) {
//打开弹窗
glfset.value = true;
}
}
}, Cesium.ScreenSpaceEventType.LEFT_DOWN);
});
</script>
<style scoped>
#cesiumContainer {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
.inputItem {
display: flex;
width: 220px;
flex-direction: column;
height: 300px;
/* justify-items: center; */
justify-content: space-around;
align-items: space-around;
padding: 10px 3px;
}
.el-input-number {
width: 160px;
}
</style>