我用mapbox结合three.js,结合官网的示例demo,实现了绘制三维物体到地图上,但是拖动地图的视角,发现三维物体的底部没有固定在地图上,会随着视角变化移动,有没有什么办法能让底部固定于地图上,不移动呢?
如图是渲染后的三维立方体俯视,我想让他们的坐标点位固定在此处
下图是拖动地图视角,发现底部的点位都偏移了
另外,官网的demo是绘制了一个三维gltf文件,无论怎么旋转视角,都能够贴合地图,代码如下
const loader = new GLTFLoader();
loader.load(
'/gltf/scene.gltf',
(gltf) => {
// 设置模型的缩放比例
gltf.scene.scale.set(10, 10, 10); // 放大10倍
this.scene.add(gltf.scene);
}
);
我只是把这部分代码替换成了three.js里的生成立方体,就出现了上述的问题
// 创建一个正方体几何体
const geometry = new THREE.BoxGeometry(10, 60, 10); // 调整尺寸大小
const material = new THREE.MeshStandardMaterial({ color: 0xff0000 }); // 设置颜色
const cube = new THREE.Mesh(geometry, material);
this.scene.add(cube);
其中坐标转换的代码,我也是按照官网的demo用的,没有做修改
//mapbox自定义图层坐标渲染
render: (gl, matrix) => {
//控制台打印该图层id开始渲染
console.log(`Custom layer rendering: ${customLayer.id}`);
const m = new THREE.Matrix4().fromArray(matrix);
const l = new THREE.Matrix4().makeTranslation(modelTransform.translateX, modelTransform.translateY, modelTransform.translateZ)
.scale(new THREE.Vector3(modelTransform.scale, -modelTransform.scale, modelTransform.scale))
.multiply(new THREE.Matrix4().makeRotationAxis(new THREE.Vector3(1, 0, 0), modelTransform.rotateX))
.multiply(new THREE.Matrix4().makeRotationAxis(new THREE.Vector3(0, 1, 0), modelTransform.rotateY))
.multiply(new THREE.Matrix4().makeRotationAxis(new THREE.Vector3(0, 0, 1), modelTransform.rotateZ));
customLayer.camera.projectionMatrix = m.multiply(l);
customLayer.renderer.resetState();
customLayer.renderer.render(customLayer.scene, customLayer.camera);
customLayer.map.triggerRepaint();
}
坐标转换方法
calculateModelTransform(point) {
const modelAsMercatorCoordinate = mapboxgl.MercatorCoordinate.fromLngLat([point.lng, point.lat], this.modelAltitude);
return {
translateX: modelAsMercatorCoordinate.x,
translateY: modelAsMercatorCoordinate.y,
translateZ: modelAsMercatorCoordinate.z,
rotateX: this.modelRotate[0],
rotateY: this.modelRotate[1],
rotateZ: this.modelRotate[2],
scale: modelAsMercatorCoordinate.meterInMercatorCoordinateUnits()
};
}
完整代码如下
<template>
<div id="map">
<div class="convertButton">
<button type="button" @click="loadModels">加载3D模型</button>
<button type="button" @click="removeModel">隐藏3D模型</button>
</div>
</div>
</template>
<script>
import mapboxgl from "../../static/mapboxgl/mapbox-gl.js";
import '../../static/mapboxgl/mapbox-gl.css';
import * as THREE from 'three';
export default {
name: "MultiThreeMap",
data() {
return {
map: null,
modelAltitude: 0,
modelRotate: [Math.PI / 2, 0, 0],
points: [
{"lng": "119.93234778808594", "lat": "30.035738991333008", "pm25": 123},
{"lng": "119.92236878808594", "lat": "30.025738991333008", "pm25": 999},
{"lng": "119.93445778808594", "lat": "30.045738991333008", "pm25": 568},
{"lng": "119.93134778808594", "lat": "30.034738991333008", "pm25": 234},
{"lng": "119.92336878808594", "lat": "30.024738991333008", "pm25": 88},
{"lng": "119.93545778808594", "lat": "30.044738991333008", "pm25": 456},
{"lng": "119.93034778808594", "lat": "30.033738991333008", "pm25": 567},
{"lng": "119.92136878808594", "lat": "30.023738991333008", "pm25": 78},
{"lng": "119.93345778808594", "lat": "30.043738991333008", "pm25": 89},
{"lng": "119.93234778808594", "lat": "30.032738991333008", "pm25": 90},
{"lng": "119.92236878808594", "lat": "30.022738991333008", "pm25": 901},
{"lng": "119.93445778808594", "lat": "30.042738991333008", "pm25": 101},
{"lng": "119.93134778808594", "lat": "30.031738991333008", "pm25": 111},
{"lng": "119.92336878808594", "lat": "30.021738991333008", "pm25": 1214},
{"lng": "119.93545778808594", "lat": "30.041738991333008", "pm25": 1315},
{"lng": "119.93034778808594", "lat": "30.030738991333008", "pm25": 1416},
{"lng": "119.92136878808594", "lat": "30.020738991333008", "pm25": 1517},
{"lng": "119.93345778808594", "lat": "30.040738991333008", "pm25": 1618},
{"lng": "119.93234778808594", "lat": "30.039738991333008", "pm25": 1719},
{"lng": "119.92236878808594", "lat": "30.029738991333008", "pm25": 1820},
{"lng": "119.93445778808594", "lat": "30.049738991333008", "pm25": 1921},
{"lng": "119.93134778808594", "lat": "30.038738991333008", "pm25": 2022},
{"lng": "119.92336878808594", "lat": "30.028738991333008", "pm25": 2123},
{"lng": "119.93545778808594", "lat": "30.048738991333008", "pm25": 2224},
{"lng": "119.93034778808594", "lat": "30.037738991333008", "pm25": 2325},
{"lng": "119.92136878808594", "lat": "30.027738991333008", "pm25": 2426},
{"lng": "119.93345778808594", "lat": "30.047738991333008", "pm25": 2527},
{"lng": "119.93234778808594", "lat": "30.036738991333008", "pm25": 2628},
{"lng": "119.92236878808594", "lat": "30.026738991333008", "pm25": 2729},
{"lng": "119.93445778808594", "lat": "30.046738991333008", "pm25": 60},
{"lng": "119.93134778808594", "lat": "30.035738991333008", "pm25": 131},
{"lng": "119.92336878808594", "lat": "30.025738991333008", "pm25": 3032},
{"lng": "119.93545778808594", "lat": "30.045738991333008", "pm25": 3133},
{"lng": "119.93034778808594", "lat": "30.034738991333008", "pm25": 34},
{"lng": "119.92136878808594", "lat": "30.024738991333008", "pm25": 35},
{"lng": "119.93345778808594", "lat": "30.044738991333008", "pm25": 36},
{"lng": "119.93234778808594", "lat": "30.033738991333008", "pm25": 37}
],
customLayers: [],
};
},
mounted() {
this.initMap();
},
methods: {
initMap() {
this.map = new mapboxgl.Map({
container: 'map', // container id
style: { // 自定义样式,指向本地瓦片源
version: 8,
sources: {
offlineTiles: {
type: 'raster',
tiles: [
'http://wprd01.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scl=1&style=7',
],
tileSize: 256,
},
},
layers: [
{
id: 'offline-layer',
type: 'raster',
source: 'offlineTiles',
},
],
},
center: [119.93445778808594, 30.045738991333008], // starting position [lng, lat]
zoom: 12,
pitch: 40,
antialias: true
});
},
loadModels() {
this.removeModel();
this.points.forEach((point, index) => {
const modelTransform = this.calculateModelTransform(point);
const customLayer = this.createCustomLayer(index, point, modelTransform);
this.customLayers.push(customLayer);
this.map.addLayer(customLayer);
});
},
createCustomLayer(index, point, modelTransform) {
let color;
let altitude;
// 根据PM2.5值设定颜色和高度
if (point.pm25 <= 50) {
color = 0x00ff00;
altitude = 200; // 低污染区域
} else if (point.pm25 <= 100) {
color = 0xffff00;
altitude = 400; // 中等污染
} else if (point.pm25 <= 200) {
color = 0xffa500;
altitude = 600; // 较高污染
} else {
color = 0xff0000;
altitude = 800; // 高污染,最高高度
}
// 计算轮廓线颜色,使其稍微暗一些
const darkerColor = this.darkerColor(color, 0.1);
const customLayer = {
id: `3d-model-${index}`,
type: 'custom',
renderingMode: '3d',
onAdd: (map, gl) => {
//控制台打印该图层id
console.log(`Custom layer added: ${customLayer.id}`);
customLayer.camera = new THREE.Camera();
customLayer.scene = new THREE.Scene();
// 添加环境光
const ambientLight = new THREE.AmbientLight(0xffffff1, 0.8); // 强度为 0.5
customLayer.scene.add(ambientLight);
// 添加定向光以增强立体感
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.5);
directionalLight.position.set(10, 10, 10).normalize();
customLayer.scene.add(directionalLight);
// 创建一个正方体几何体
const geometry = new THREE.BoxGeometry(20, altitude, 20); // 调整尺寸大小
const material = new THREE.MeshStandardMaterial({
color: color,// 设置颜色
transparent: true, // 开启透明效果
opacity: 0.8 // 设置透明度,范围从0到1
});
const cube = new THREE.Mesh(geometry, material);
// 添加轮廓线
const edgeGeometry = new THREE.EdgesGeometry(geometry);
const lineMaterial = new THREE.LineBasicMaterial({
color: darkerColor,// 设置轮廓线的颜色和宽度
});
const lines = new THREE.LineSegments(edgeGeometry, lineMaterial);
cube.add(lines);
customLayer.scene.add(cube);
customLayer.map = map;
customLayer.renderer = new THREE.WebGLRenderer({
canvas: map.getCanvas(),
context: gl,
antialias: true
});
customLayer.renderer.autoClear = false;
},
render: (gl, matrix) => {
//控制台打印该图层id开始渲染
console.log(`Custom layer rendering: ${customLayer.id}`);
const m = new THREE.Matrix4().fromArray(matrix);
const l = new THREE.Matrix4().makeTranslation(modelTransform.translateX, modelTransform.translateY, modelTransform.translateZ)
.scale(new THREE.Vector3(modelTransform.scale, -modelTransform.scale, modelTransform.scale))
.multiply(new THREE.Matrix4().makeRotationAxis(new THREE.Vector3(1, 0, 0), modelTransform.rotateX))
.multiply(new THREE.Matrix4().makeRotationAxis(new THREE.Vector3(0, 1, 0), modelTransform.rotateY))
.multiply(new THREE.Matrix4().makeRotationAxis(new THREE.Vector3(0, 0, 1), modelTransform.rotateZ));
customLayer.camera.projectionMatrix = m.multiply(l);
customLayer.renderer.resetState();
customLayer.renderer.render(customLayer.scene, customLayer.camera);
customLayer.map.triggerRepaint();
}
};
return customLayer;
},
// 计算颜色稍微暗一点的方法
darkerColor(color, factor) {
const r = Math.max(0, Math.floor((color >> 16) & 0xFF * (1 - factor)));
const g = Math.max(0, Math.floor((color >> 8) & 0xFF * (1 - factor)));
const b = Math.max(0, Math.floor(color & 0xFF * (1 - factor)));
return (r << 16) + (g << 8) + b;
},
removeModel() {
this.customLayers.forEach(layer => {
this.map.removeLayer(layer.id);
});
this.customLayers = [];
},
calculateModelTransform(point) {
const modelAsMercatorCoordinate = mapboxgl.MercatorCoordinate.fromLngLat([point.lng, point.lat], this.modelAltitude);
return {
translateX: modelAsMercatorCoordinate.x,
translateY: modelAsMercatorCoordinate.y,
translateZ: modelAsMercatorCoordinate.z,
rotateX: this.modelRotate[0],
rotateY: this.modelRotate[1],
rotateZ: this.modelRotate[2],
scale: modelAsMercatorCoordinate.meterInMercatorCoordinateUnits()
};
}
},
beforeDestroy() {
if (this.map) {
this.map.remove();
}
},
}
</script>
<style scoped>
#map {
position: absolute;
top: 0;
bottom: 0;
width: 100%;
}
.convertButton {
position: relative;
top: 10px;
margin: 0 50px;
padding: 5px;
border-radius: 3px;
z-index: 999;
background-color: rgba(0, 168, 0, 0);
}
</style>