第一步,安装插件:
npm install cesium --save
第二步,移动文件:
在node-modules中找到刚刚安装的cesium,在文件夹中里面有一个Build文件夹,把Build里面的Cesium复制出来丢到public中
第三步,在public/index.html中,引入cesium全局样式和cesium源码,这样前期工作就已经完成了
<link rel="stylesheet" href="./Cesium/Widgets/widgets.css"> //head部分<script src="./Cesium/Cesium.js"></script> //script部分
第四步,在官网申请一个token(可用默认token):
Cesium 官网:Cesium: The Platform for 3D Geospatial
第五步,开始写代码 (cesium部分)
一、初始化地球
mounted() {
this.init();
},
methods: {
init() {
this.viewer = new Cesium.Viewer("my-map", {
homeButton: false,
sceneModePicker: false,
baseLayerPicker: false, // 影像切换
animation: true, // 是否显示动画控件
infoBox: false, // 是否显示点击要素之后显示的信息
selectionIndicator: false, // 要素选中框
geocoder: false, // 是否显示地名查找控件
timeline: true, // 是否显示时间线控件
fullscreenButton: false,
shouldAnimate: false,
navigationHelpButton: false, // 是否显示帮助信息控件
});
},
},
二、优化(添加属性及其他)
methods: {
init() {
(Cesium.Ion.defaultAccessToken = "你申请的cesium的token");
this.viewer = new Cesium.Viewer("my-map", {
homeButton: false,
sceneModePicker: false,
baseLayerPicker: false, // 影像切换
animation: true, // 是否显示动画控件
infoBox: false, // 是否显示点击要素之后显示的信息
selectionIndicator: false, // 要素选中框
geocoder: false, // 是否显示地名查找控件
timeline: true, // 是否显示时间线控件
fullscreenButton: false,
shouldAnimate: false,
navigationHelpButton: false, // 是否显示帮助信息控件
});
// 优化第一步
//这是让你的画面以一个怎样的形式出现,相当于出场动画
this.viewer.camera.flyTo({
// fromDegrees()方法,将经纬度和高程转换为世界坐标,这里定位到中国
destination: Cesium.Cartesian3.fromDegrees(101.8, 33.74, 5000000),
orientation: {
// 指向
// heading:Cesium.Math.toRadians(90,0),
// 视角
// pitch:Cesium.Math.toRadians(-90),
roll: 0.0,
},
});
// 优化第二步
//显示标注
this.viewer.imageryLayers.addImageryProvider(
new Cesium.WebMapTileServiceImageryProvider({
url:
"http://{s}.tianditu.gov.cn/cva_c/wmts?service=wmts&request=GetTile&version=1.0.0" +
"&LAYER=cva&tileMatrixSet=c&TileMatrix={TileMatrix}&TileRow={TileRow}&TileCol={TileCol}" +
"&style=default&format=tiles&tk=0a30a060a83ae06ba7a9dd5f70a3c203",
layer: "tdtCva",
style: "default",
format: "tiles",
tileMatrixSetID: "c",
subdomains: ["t0", "t1", "t2", "t3", "t4", "t5", "t6", "t7"],
tilingScheme: new Cesium.GeographicTilingScheme(),
tileMatrixLabels: [
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"10",
"11",
"12",
"13",
"14",
"15",
"16",
"17",
"18",
"19",
],
maximumLevel: 18,
show: false,
})
);
},
},
三、实现添加模型
addModel(viewer, url1, Cesium) { // 添加模型 const position = Cesium.Cartesian3.fromDegrees( 106.613922, 29.53832, 0.0 ); const heading = Cesium.Math.PI/2 // Cesium.Math.toRadians(135); const pitch = Cesium.Math.PI/2; const roll = Cesium.Math.PI/2; const hpr = new Cesium.HeadingPitchRoll(heading, pitch, roll); const orientation = Cesium.Transforms.headingPitchRollQuaternion( position, hpr ); const entity = viewer.entities.add({ name: 'MyName', position: position, orientation: orientation, model: { uri: url1, minimumPixelSize: 0, maximumScale: 20000, scale: 1, // 缩放比例 incrementallyLoadTextures: true, // 加载模型后的纹理是否可以继续流入 runAnimations: true, // 指定是否应运行 glTF 动画 clampAnimations: true, // 指定 glTF 动画是否应在不带关键帧的持续时间内保持最后一个姿势 shadows: Cesium.ShadowMode.ENABLED, // 指定模型是否 投射或接收来自光源的阴影 // heightReference: Cesium.HeightReference.NONE, // 表示相对于地形的位置。NONE表示绝对位置 heightReference: Cesium.HeightReference.CLAMP_TO_GROUND // 相对位置 } }); viewer.trackedEntity = entity; },
第六步,开始写代码 (three.js部分)
一、初始化three.js
initthree(callback, callback1) { // 创建场景、相机和渲染 const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); const renderer = new THREE.WebGLRenderer({ antialiasing: true, alpha: true }); renderer.setSize(window.innerWidth, window.innerHeight); const container = document.getElementById('id'); container.appendChild(renderer.domElement); const controls = new OrbitControls(camera, renderer.domElement); controls.addEventListener('change', () => { renderer.render(scene, camera) // 监听鼠标,键盘事件 }) // 创建灯光 const ambientLight = new THREE.AmbientLight(0x404040); scene.add(ambientLight); const directionalLight = new THREE.DirectionalLight(0xffffff); directionalLight.position.set(1, 1, 1).normalize(); scene.add(directionalLight); function animate() { requestAnimationFrame(animate); renderer.render(scene, camera); } // 获取从父组件传来的模型文件 const src = lodash.castArray(['Mypath']) // 创建加载器 const loader = new PLYLoader(); loader.load(src, geometry => { // 创建网格 const color = geometry.color const material = new THREE.MeshStandardMaterial({color, roughness: 0.8, metalness: 0.2}); const mesh = new THREE.Mesh(geometry, material); scene.add(mesh); }) // 添加坐标轴 const axesHelper = new THREE.AxesHelper(300); // 300是坐标轴的长度 scene.add(axesHelper); camera.position.z = 5; animate(); }
二、给three增加打点、连线功能
methods: { showpointsThree(pointslist, color, scene) { if (pointslist == null) { return } // 打点 pointslist.forEach(point => { const sphereGeometry = new THREE.SphereGeometry(1, 32, 32); // 半径为5,细分度32x32 const sphereMaterial = new THREE.MeshBasicMaterial({color: color}); // 红色 const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial); sphere.position.set(...point); // 设置球体的位置 scene.add(sphere); // 将 球体添加到场景中 }); // 计算并添加两点之间的线段 if (pointslist.length % 2 == 0) { for (let i = 0; i < pointslist.length - 1; i += 2) { const startPoint = pointslist[i]; const endPoint = pointslist[i + 1]; // 创建线段的几何体 const geometry = new THREE.BufferGeometry().setFromPoints([ new THREE.Vector3(...startPoint), new THREE.Vector3(...endPoint) ]); // 创建线段的材料(这里使用LineBasicMaterial) const material = new THREE.LineBasicMaterial({color: color}); // 蓝色 // 创建线段并添加到场景中 const line = new THREE.Line(geometry, material); scene.add(line); } } // 做标记 }, // 构建three场景,打包scene initthree(callback, callback1) { // 创建场景、相机和渲染 const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); const renderer = new THREE.WebGLRenderer({ antialiasing: true, alpha: true }); renderer.setSize(window.innerWidth, window.innerHeight); const container = document.getElementById('id'); container.appendChild(renderer.domElement); const controls = new OrbitControls(camera, renderer.domElement); controls.addEventListener('change', () => { renderer.render(scene, camera) // 监听鼠标,键盘事件 }) // 创建灯光 const ambientLight = new THREE.AmbientLight(0x404040); scene.add(ambientLight); const directionalLight = new THREE.DirectionalLight(0xffffff); directionalLight.position.set(1, 1, 1).normalize(); scene.add(directionalLight); function animate() { requestAnimationFrame(animate); renderer.render(scene, camera); } // 获取从父组件传来的模型文件 const src = lodash.castArray(['Mypath']) // 创建加载器 const loader = new PLYLoader(); loader.load(src, geometry => { // 创建网格 const color = geometry.color const material = new THREE.MeshStandardMaterial({color, roughness: 0.8, metalness: 0.2}); const mesh = new THREE.Mesh(geometry, material); const exporter = new GLTFExporter(); exporter.parse(mesh, (result) => { const blob = new Blob([result], {type: 'application/octet-stream'}); const url = URL.createObjectURL(blob); callback(url) }, {binary: true}); // scene.add(mesh); }) // 给一些假定的点 const datalist = [给定一些坐标] // 打点 this.showpointsThree(datalist, 0xff0000, scene) // 添加坐标轴 const axesHelper = new THREE.AxesHelper(300); // 300是坐标轴的长度 scene.add(axesHelper); camera.position.z = 5; animate(); const exporter = new GLTFExporter(); exporter.parse(scene, (result) => { const blob = new Blob([result], {type: 'application/octet-stream'}); const url = URL.createObjectURL(blob); callback1(url) }, {binary: true}); } }
第七步,将three.js加载的模型导出
将three.js加载的模型导出成cesium可以加载的格式(如.glb)
一、GLTFExporter
GLTFExporter是一个用于将3D场景导出为glTF格式的JavaScript库
// 导入GLTFExporter库
import { GLTFExporter } from 'three/examples/jsm/exporters/GLTFExporter.js';// 创建GLTFExporter对象
const exporter = new GLTFExporter();// 定义导出选项(可选)
const options = {
binary: true, // 是否以二进制格式输出(默认为false,输出为.gltf文件)
includeCustomExtensions: true, // 是否包含自定义扩展信息
trs: true, // 是否将几何体的位置、旋转和缩放信息导出
animations: [], // 要导出的动画(如果有的话)
embedImages: false // 是否将图片嵌入到gltf文件中
};// 使用parse方法导出场景为glTF格式
exporter.parse(scene, options, function (result) {
// 处理导出的结果
if (result instanceof ArrayBuffer) {
// 以二进制格式输出
saveArrayBuffer(result, 'scene.glb');
} else {
// 以JSON格式输出
const output = JSON.stringify(result, null, 2);
saveString(output, 'scene.gltf');
}
}, function (error) {
// 处理导出过程中出现的错误
console.error('Export error:', error);
});// 保存导出的文件
function saveArrayBuffer(buffer, filename) {
// 实现保存二进制文件的逻辑
}function saveString(text, filename) {
// 实现保存JSON文件的逻辑
}
一般使用exporter.parse即可,导出后会生成cesium可识别的类型的url,将此url放入cesium加载模型的uri中即可。但这样做会出现一个很奇怪的错误,以我目前的能力还解决不了,如果有大佬能解决,欢迎评论!
二、问题与解决(简陋解决)
问题:在GLTFExporter导出的url本想以this.url = url给data定义的变量,但这种方式刚开始拿不到这个url,然而执行完cesium后又拿到了。基于这个问题,想到了其他解决方案,如下:
initthree(callback, callback1) { // 创建场景、相机和渲染 const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); const renderer = new THREE.WebGLRenderer({ antialiasing: true, alpha: true }); renderer.setSize(window.innerWidth, window.innerHeight); const container = document.getElementById('id'); container.appendChild(renderer.domElement); const controls = new OrbitControls(camera, renderer.domElement); controls.addEventListener('change', () => { renderer.render(scene, camera) // 监听鼠标,键盘事件 }) // 创建灯光 const ambientLight = new THREE.AmbientLight(0x404040); scene.add(ambientLight); const directionalLight = new THREE.DirectionalLight(0xffffff); directionalLight.position.set(1, 1, 1).normalize(); scene.add(directionalLight); function animate() { requestAnimationFrame(animate); renderer.render(scene, camera); } // 获取从父组件传来的模型文件 const src = lodash.castArray(['Mypath']) // 创建加载器 const loader = new PLYLoader(); loader.load(src, geometry => { // 创建网格 const color = geometry.color const material = new THREE.MeshStandardMaterial({color, roughness: 0.8, metalness: 0.2}); const mesh = new THREE.Mesh(geometry, material); const exporter = new GLTFExporter(); exporter.parse(mesh, (result) => { const blob = new Blob([result], {type: 'application/octet-stream'}); const url = URL.createObjectURL(blob); callback(url) }, {binary: true}); // scene.add(mesh); }) const datalist = [给定一些坐标] // 打点 this.showpointsThree(datalist, 0xff0000, scene) // 添加坐标轴 const axesHelper = new THREE.AxesHelper(300); // 300是坐标轴的长度 scene.add(axesHelper); camera.position.z = 5; animate(); const exporter = new GLTFExporter(); exporter.parse(scene, (result) => { const blob = new Blob([result], {type: 'application/octet-stream'}); const url = URL.createObjectURL(blob); callback1(url) }, {binary: true}); }
定义两个 callback(url)回传url的值,这样就可以拿到了。但还有一个问题是exporter.parse导出场景时好像不会把mesh里的模型一起导出,所以就定义了两个exporter,如果大佬们有更好的方法欢迎指点!
所有代码
<template> <div> <div id="my-map"></div> <div id="id"/> </div> </template> <script> import lodash from 'lodash' // import * as Cesium from 'cesium' import {GLTFExporter} from 'three/examples/jsm/exporters/GLTFExporter'; import * as THREE from 'three'; import {OrbitControls} from 'three/examples/jsm/controls/OrbitControls'; import {PLYLoader} from 'three/examples/jsm/loaders/PLYLoader'; export default { name: 'cesium', components: {}, data() { return { } }, mounted() { // this.initthree(); this.initCesium(); }, methods: { showpointsThree(pointslist, color, scene) { if (pointslist == null) { return } // 打点 pointslist.forEach(point => { const sphereGeometry = new THREE.SphereGeometry(1, 32, 32); // 半径为5,细分度32x32 const sphereMaterial = new THREE.MeshBasicMaterial({color: color}); // 红色 const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial); sphere.position.set(...point); // 设置球体的位置 scene.add(sphere); // 将 球体添加到场景中 }); // 计算并添加两点之间的线段 if (pointslist.length % 2 == 0) { for (let i = 0; i < pointslist.length - 1; i += 2) { const startPoint = pointslist[i]; const endPoint = pointslist[i + 1]; // 创建线段的几何体 const geometry = new THREE.BufferGeometry().setFromPoints([ new THREE.Vector3(...startPoint), new THREE.Vector3(...endPoint) ]); // 创建线段的材料(这里使用LineBasicMaterial) const material = new THREE.LineBasicMaterial({color: color}); // 蓝色 // 创建线段并添加到场景中 const line = new THREE.Line(geometry, material); scene.add(line); } } // 做标记 }, addModel(viewer, url1, Cesium) { // 添加模型 const position = Cesium.Cartesian3.fromDegrees( 106.613922, 29.53832, 0.0 ); const heading = Cesium.Math.PI/2// Cesium.Math.toRadians(135); const pitch = Cesium.Math.PI/2; const roll = Cesium.Math.PI/2; const hpr = new Cesium.HeadingPitchRoll(heading, pitch, roll); const orientation = Cesium.Transforms.headingPitchRollQuaternion( position, hpr ); const entity = viewer.entities.add({ name: 'MyName', position: position, orientation: orientation, model: { uri: url1, minimumPixelSize: 0, maximumScale: 20000, scale: 1, // 缩放比例 incrementallyLoadTextures: true, // 加载模型后的纹理是否可以继续流入 runAnimations: true, // 指定是否应运行 glTF 动画 clampAnimations: true, // 指定 glTF 动画是否应在不带关键帧的持续时间内保持最后一个姿势 shadows: Cesium.ShadowMode.ENABLED, // 指定模型是否 投射或接收来自光源的阴影 // heightReference: Cesium.HeightReference.NONE, // 表示相对于地形的位置。NONE表示绝对位置 heightReference: Cesium.HeightReference.CLAMP_TO_GROUND // 相对位置 } }); viewer.trackedEntity = entity; }, initEntityLine(Cesium, viewer) { const entities = viewer.entities // 创建两个点的位置 let startPoint = Cesium.Cartesian3.fromDegrees(106.613922, 29.53832); let endPoint = Cesium.Cartesian3.fromDegrees(106.613922, 28.53822); // 创建线的实体 entities.add({ polyline: { positions: [startPoint, endPoint], width: 2, material: Cesium.Color.RED, heightReference: Cesium.HeightReference.CLAMP_TO_GROUND // 相对位置 } }); }, addPoint(viewer, options) { const {name, longitude, latitude, altitude} = options; const position = Cesium.Cartesian3.fromDegrees(longitude, latitude, altitude); viewer.entities.add({ name: name, position: position, point: { pixelSize: 10, color: Cesium.Color.RED, outlineColor: Cesium.Color.BLACK, outlineWidth: 1, heightReference: Cesium.HeightReference.CLAMP_TO_GROUND // 相对位置 } }); }, initCesium() { (Cesium.Ion.defaultAccessToken = 'MyToken'); const viewer = new Cesium.Viewer('my-map', { // 层高图形 terrain: Cesium.Terrain.fromWorldTerrain({ requestVertexNormals: true, // 开启地形光照 requestWaterMask: false // 开启水面波纹 }), sceneMode: window.Cesium.SceneMode.SCENE3D, homeButton: false, vrButton: true, sceneModePicker: false, baseLayerPicker: false, // 影像切换 animation: false, // 是否显示动画控件 infoBox: false, // 是否显示点击要素之后显示的信息 selectionIndicator: false, // 要素选中框 geocoder: true, // 是否显示地名查找控件 timeline: false, // 是否显示时间线控件 fullscreenButton: false, shouldAnimate: false, navigationHelpButton: false // 是否显示帮助信息控件 }); // 添加点 this.addPoint(viewer, { name: '点', longitude: 106.613922, latitude: 29.53832, altitude: 0 }); // 隐藏版权信息 viewer.cesiumWidget.creditContainer.setAttribute('style', 'display:none') // 优化第一步 // 这是让你的画面以一个怎样的形式出现,相当于出场动画 /* viewer.camera.flyTo({ // fromDegrees()方法,将经纬度和高程转换为世界坐标,这里定位到中国 //destination: Cesium.Cartesian3.fromDegrees(106.613922, 29.53832, 1000), orientation: { // 指向 // heading:Cesium.Math.toRadians(90,0), // 视角 // pitch:Cesium.Math.toRadians(-90), roll: 0.0 } });*/ // 优化第二步 // 显示标注 viewer.imageryLayers.addImageryProvider( new Cesium.WebMapTileServiceImageryProvider({ url: 'http://{s}.tianditu.gov.cn/cva_c/wmts?service=wmts&request=GetTile&version=1.0.0' + '&LAYER=cva&tileMatrixSet=c&TileMatrix={TileMatrix}&TileRow={TileRow}&TileCol={TileCol}' + '&style=default&format=tiles&tk=0a30a060a83ae06ba7a9dd5f70a3c203', layer: 'tdtCva', style: 'default', format: 'tiles', tileMatrixSetID: 'c', subdomains: ['t0', 't1', 't2', 't3', 't4', 't5', 't6', 't7'], tilingScheme: new Cesium.GeographicTilingScheme(), tileMatrixLabels: [ '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19' ], maximumLevel: 18, show: false }) ); this.initthree((exportedUrl) => { console.log('从 three_init 返回的 URL:', exportedUrl); // this.show_model(exportedUrl, this.viewer, Cesium) // 添加模型 this.addModel(viewer, exportedUrl, Cesium) }, (exportedUrl1) => { console.log('从 three_init 返回的 URL:', exportedUrl1); // this.show_model(exportedUrl, this.viewer, Cesium) // 添加模型 this.addModel(viewer, exportedUrl1, Cesium) }); }, // 构建three场景,打包scene initthree(callback, callback1) { // 创建场景、相机和渲染 const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); const renderer = new THREE.WebGLRenderer({ antialiasing: true, alpha: true }); renderer.setSize(window.innerWidth, window.innerHeight); const container = document.getElementById('id'); container.appendChild(renderer.domElement); const controls = new OrbitControls(camera, renderer.domElement); controls.addEventListener('change', () => { renderer.render(scene, camera) // 监听鼠标,键盘事件 }) // 创建灯光 const ambientLight = new THREE.AmbientLight(0x404040); scene.add(ambientLight); const directionalLight = new THREE.DirectionalLight(0xffffff); directionalLight.position.set(1, 1, 1).normalize(); scene.add(directionalLight); function animate() { requestAnimationFrame(animate); renderer.render(scene, camera); } // 获取从父组件传来的模型文件 const src = lodash.castArray(['Mypath']) // 创建加载器 const loader = new PLYLoader(); loader.load(src, geometry => { // 创建网格 const color = geometry.color const material = new THREE.MeshStandardMaterial({color, roughness: 0.8, metalness: 0.2}); const mesh = new THREE.Mesh(geometry, material); const exporter = new GLTFExporter(); exporter.parse(mesh, (result) => { const blob = new Blob([result], {type: 'application/octet-stream'}); const url = URL.createObjectURL(blob); callback(url) }, {binary: true}); // scene.add(mesh); }) const datalist = [给定数据] // 打点 this.showpointsThree(datalist, 0xff0000, scene) // 添加坐标轴 const axesHelper = new THREE.AxesHelper(300); // 300是坐标轴的长度 scene.add(axesHelper); camera.position.z = 5; animate(); const exporter = new GLTFExporter(); exporter.parse(scene, (result) => { const blob = new Blob([result], {type: 'application/octet-stream'}); const url = URL.createObjectURL(blob); callback1(url) }, {binary: true}); } } } </script>