2022年7月17日
经过一到两个礼拜的对前端可视化的了解与学习,开始着手于3D智慧城市的项目
话不多说先看效果图:
导入底层地图
//创建底图
var map = new mapboxgl.Map({
style:'mapbox://styles/mapbox/navigation-guidance-night-v2',//风格样式
center: [XXXX.XXXX,XXXX,XXXX],//地图中心
zoom: 15.5,//起始高度
pitch: 45,//视觉角度
container: 'map',//加载地图
});
添加旋转
//获取时间戳来实现旋转
function rotateCamera(timestamp) {
map.rotateTo((timestamp / 100) % 360, {duration: 0});
requestAnimationFrame(rotateCamera);//requestAnimationFrame接口实现渲染
}
将threejs对象的位置转换成地图位置
- 设置坐标基点
- Y轴转换
- 经纬度坐标转换
新建一个用户层,在该层添加建筑,精灵图,飞线等。
老规矩,threejs三大件,场景,相机,渲染器。
this.camera = new THREE.Camera();//添加相机
this.scene = new THREE.Scene();//添加场景
var directionalLight = new THREE.DirectionalLight(0xffffff,6,0);//设置灯光1
directionalLight.position.set(3.8, 1, 1).normalize();//设置灯光位置
this.scene.add(directionalLight);//添加灯光1
var directionalLight2 = new THREE.DirectionalLight(0xffffff,6,0);//设置灯光2
directionalLight2.position.set(-3, -1, -1).normalize();//设置灯光位置
this.scene.add(directionalLight2);//添加灯光2
//添加渲染器
this.renderer = new THREE.WebGLRenderer({
canvas: map.getCanvas(),//threejs的画布即为mapbox的地图
context: gl,
antialias: true//抗锯齿打开
});
通过threejs的加载器导入地标建筑
loader.load('XXXXXX.gltf', (function (gltf) {
this.scene.add(gltf.scene);
}).bind(this));
构造三维三次贝塞尔曲线(CubicBezierCurve3)实现飞线
const curve = new THREE.CubicBezierCurve3(
new THREE.Vector3(0, 0, 0),//起始位置
new THREE.Vector3(20, 15, 0),//飞线形状
new THREE.Vector3(0, 150, 0),//飞线形状
new THREE.Vector3(-70, 10, 0)//终点位置
);
const pointsPosition = curve.getPoints(5000); // 点数组,5000个采样点
const lineLength = 3000 // 飞线长度,800个点
let currentIndex = 0 // 当前索引
const circle = 3000 // 周期3秒
const startTime = Date.now() // 开始时间
// 创建缓冲几何体
const pointsGeometry = new THREE.BufferGeometry();
setInterval(() => {
const now = Date.now() // 当前时间
currentIndex = (now - startTime) % circle / circle * pointsPosition.length // 当前索引
currentIndex = Math.floor(currentIndex) // 索引修正
if (currentIndex > pointsPosition.length) currentIndex = 0 // 索引修正
// 飞线点位数组
const lineArr = pointsPosition.filter((point, index) => {
if (index >= currentIndex && index <= currentIndex + lineLength) return true
})
const arr = lineArr.reduce((arr, point) => {
const { x, y, z } = point
arr.push(x, y, z)
return arr
}, [])
// 几何体中添加位置属性
pointsGeometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array(arr), 3)); // 一个顶点由3个坐标构成
// 几何体添加图元尺寸的缩放属性
const aScaleArr = lineArr.map((point, index) => { return index / lineLength })
pointsGeometry.setAttribute('aScale', new THREE.BufferAttribute(new Float32Array(aScaleArr), 1)); // 一个缩放量由1个浮点数表示
}, 50)
// 纹理和材质
const pointTexture = new THREE.TextureLoader().load("XXXXXXXXX.png");
const pointsMaterial = new THREE.PointsMaterial({
color: '#FFFF00',
map: pointTexture, // 材质含有透明度
transparent: true, // 开启透明度
depthWrite: false, // 禁用深度写入
size: 70,// 点大小
sizeAttenuation: true, // 大小是否随相机深度衰减
});
// 修正图元大小
pointsMaterial.onBeforeCompile = (shader) => {
const vertex = `
attribute float aScale;
void main() {
`
const vertex1 = `gl_PointSize = size * aScale;`
shader.vertexShader = shader.vertexShader.replace("void main() {", vertex);
shader.vertexShader = shader.vertexShader.replace("gl_PointSize = size;", vertex1);
}
// 创建点系统,加入场景
const points = new THREE.Points(pointsGeometry, pointsMaterial);
this.scene.add(points);
// 图元重新排序
setInterval(() => {
// 提取点
const position = pointsGeometry.attributes.position
const scale = pointsGeometry.attributes.aScale // 缩放属性
const { count, array, itemSize } = position
const pointArr = []
for (let i = 0; i < count; i++) {
const point = {
position: [array[i * itemSize + 0], array[i * itemSize + 1], array[i * itemSize + 2],],
scale: scale.array[i]
}
pointArr.push(point)
}
// 重排序
pointArr.sort((point2, point1) => {
const v1 = new THREE.Vector3(...point1.position)
const v2 = new THREE.Vector3(...point2.position)
if (v1.applyMatrix4(points.modelViewMatrix).z > v2.applyMatrix4(points.modelViewMatrix).z) return -1 // 交互顺序
return 1 // 不交换顺序
})
// 更新几何体的position位置
const arr = pointArr.reduce((arr, point) => {
arr.push(...point.position)
return arr
}, [])
pointsGeometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array(arr), 3)); // 一个顶点由3个坐标构成
// 更新几何体的aScale数据
const arr1 = pointArr.reduce((arr, point) => {
arr.push(point.scale)
return arr
}, [])
pointsGeometry.setAttribute('aScale', new THREE.BufferAttribute(new Float32Array(arr1), 1)); // 一个缩放量由1个浮点数表示
}, 50)
用mapbox自带图层实现精灵图
map.loadImage(
'XXXXXXXX.PNG',//导入精灵图
function(error, image) {
if (error) throw error;//错误类型抛出
map.addImage('spirit1', image);//重命名
map.addLayer({
"id": "points",//id
"type": "symbol",//类型
//精灵图相关属性设置
"source": {
"type": "geojson",
"data": {
"type": "FeatureCollection",
"features": [{
"type": "Feature",
"properties": {
"MyBuildName": ` 区域:地方本部
状态:正常`
},
"geometry": {
"type": "Point",
"coordinates": [121.155361299823,30.051971308151167],
}
}]
}
},
//字体样式设置
"layout": {
"text-field": "{MyBuildName}",//加载文本
"text-offset": [0, -2.2],//设置图标与图标注相对之间的距离
"text-anchor": "top",//标记文本相对于定位点的位置
"text-size": 15,//字号
"icon-image": "spirit1",//选择图片
"icon-size": 0.3,//精灵图大小
},
//字体颜色设置
paint: {
"text-color": "#FFFFFF"
}
});
});
以此类推创建4个飞线,已经与之对应的4个精灵图
最后导入城市建筑的geojson文件,通过自定义图层加载城市建筑
//导入geojson数据
var url = "city.geojson"
var request = new XMLHttpRequest();
request.open("get", url);/*设置请求方法与路径*/
request.send(null);/*不发送数据到服务器*/
request.onload = function () {/*XHR对象获取到返回信息后执行*/
if (request.status == 200) {/*返回状态为200,即为数据获取成功*/
var json = JSON.parse(request.responseText);
for (var i = 0; i < json.length; i++) {
console.log(json[i].name);
}
console.log(json);
addBuildingLayer(json)
// 添加自定义的gltb模型图层
map.addLayer(customLayer);
}
}
//设置建筑高度
feature.features.forEach((item) => {
let he //默认建筑物高度
if (item.properties.height === '') {
he = '20'
} else {
he = item.properties.height
}
//模拟数据
//item.properties.pkid = parseInt(item.properties.xh)
item.properties.height = parseInt(he)
item.properties.base_height = parseInt(0)
})
//自定义图层
map.addLayer({
id: 'entity_layer',
source: 'states',
type: 'fill-extrusion',
layout: {},
minzoom: 5,
paint: {
'fill-extrusion-color': '#FFF111',
'fill-extrusion-height': [
'interpolate',
['linear'],
['zoom'],
14,
0,
14.05,
['get', 'height'],
],
'fill-extrusion-base': [
'interpolate',
['linear'],
['zoom'],
14,
0,
14.05,
['get', 'base_height'],
],
'fill-extrusion-opacity': 0.5,
},
});
以为后期团队调整路线,所以mapbox+threejs的实现方案只做到了目前效果。
接下一章便是开启maptalks+threejs的3D城市方案路线。
本文含有隐藏内容,请 开通VIP 后查看