如何将轨迹回放中的小车图层改成3D模型呢?
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Add a 3D model</title>
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no">
<link href="https://api.mapbox.com/mapbox-gl-js/v3.7.0/mapbox-gl.css" rel="stylesheet">
<script src="https://api.mapbox.com/mapbox-gl-js/v3.7.0/mapbox-gl.js"></script>
<style>
body {
margin: 0;
padding: 0;
}
#map {
position: absolute;
top: 0;
bottom: 0;
width: 100%;
}
</style>
</head>
<body>
<script src="https://unpkg.com/three@0.126.0/build/three.min.js"></script>
<script src="https://unpkg.com/three@0.126.0/examples/js/loaders/GLTFLoader.js"></script>
<div id="map"></div>
<script>
mapboxgl.accessToken = 'pk.eyJ1Ijoiaml5ZXdlbiIsImEiOiJjbG5qeW9nZTIxbmtlMm1ybHVzMW04djh5In0._6aML1rN-1KVGurR9aIVCg';
const map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/light-v11',
zoom: 18,
center: [148.9819, -35.3981],
pitch: 60,
antialias: true
});
const modelOrigin = [148.9819, -35.39847];
const modelAltitude = 0;
const modelRotate = [Math.PI / 2, 0, 0];
const modelAsMercatorCoordinate = mapboxgl.MercatorCoordinate.fromLngLat(
modelOrigin,
modelAltitude
);
const modelTransform = {
translateX: modelAsMercatorCoordinate.x,
translateY: modelAsMercatorCoordinate.y,
translateZ: modelAsMercatorCoordinate.z,
rotateX: modelRotate[0],
rotateY: modelRotate[1],
rotateZ: modelRotate[2],
scale: modelAsMercatorCoordinate.meterInMercatorCoordinateUnits()
};
const THREE = window.THREE;
const customLayer = {
id: '3d-model',
type: 'custom',
renderingMode: '3d',
onAdd: function (map, gl) {
this.camera = new THREE.Camera();
this.scene = new THREE.Scene();
const directionalLight = new THREE.DirectionalLight(0xffffff);
directionalLight.position.set(0, -70, 100).normalize();
this.scene.add(directionalLight);
const directionalLight2 = new THREE.DirectionalLight(0xffffff);
directionalLight2.position.set(0, 70, 100).normalize();
this.scene.add(directionalLight2);
const loader = new THREE.GLTFLoader();
loader.load(
'https://docs.mapbox.com/mapbox-gl-js/assets/34M_17/34M_17.gltf',
(gltf) => {
this.model = gltf.scene;
this.scene.add(this.model);
}
);
this.map = map;
this.renderer = new THREE.WebGLRenderer({
canvas: map.getCanvas(),
context: gl,
antialias: true
});
this.renderer.autoClear = false;
map.on('click', (e) => {
const clickedLngLat = [e.lngLat.lng, e.lngLat.lat];
const clickedMercatorCoordinate = mapboxgl.MercatorCoordinate.fromLngLat(
clickedLngLat,
modelAltitude
);
const targetPosition = {
x: clickedMercatorCoordinate.x,
y: clickedMercatorCoordinate.y,
z: clickedMercatorCoordinate.z
};
this.moveModelTo(targetPosition);
});
},
moveModelTo: function (targetPosition) {
const duration = 1000;
const steps = 60;
const stepTime = duration / steps;
const originalPosition = {
x: modelTransform.translateX,
y: modelTransform.translateY,
z: modelTransform.translateZ
};
let stepCount = 0;
const animate = () => {
if (stepCount < steps) {
const progress = stepCount / steps;
modelTransform.translateX = originalPosition.x + (targetPosition.x - originalPosition.x) * progress;
modelTransform.translateY = originalPosition.y + (targetPosition.y - originalPosition.y) * progress;
modelTransform.translateZ = originalPosition.z + (targetPosition.z - originalPosition.z) * progress;
stepCount++;
requestAnimationFrame(animate);
}
};
animate();
},
render: function (gl, matrix) {
const rotationX = new THREE.Matrix4().makeRotationAxis(
new THREE.Vector3(1, 0, 0),
modelTransform.rotateX
);
const rotationY = new THREE.Matrix4().makeRotationAxis(
new THREE.Vector3(0, 1, 0),
modelTransform.rotateY
);
const rotationZ = new THREE.Matrix4().makeRotationAxis(
new THREE.Vector3(0, 0, 1),
modelTransform.rotateZ
);
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(rotationX)
.multiply(rotationY)
.multiply(rotationZ);
this.camera.projectionMatrix = m.multiply(l);
this.renderer.resetState();
this.renderer.render(this.scene, this.camera);
this.map.triggerRepaint();
}
};
map.on('style.load', () => {
map.addLayer(customLayer, 'waterway-label');
});
</script>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Add a 3D model</title>
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no">
<link href="https://api.mapbox.com/mapbox-gl-js/v3.7.0/mapbox-gl.css" rel="stylesheet">
<script src="https://api.mapbox.com/mapbox-gl-js/v3.7.0/mapbox-gl.js"></script>
<style>
body {
margin: 0;
padding: 0;
}
#map {
position: absolute;
top: 0;
bottom: 0;
width: 100%;
}
#controls {
position: fixed;
z-index: 2;
top: 10px;
left: 10px;
background: white;
padding: 10px;
border-radius: 5px;
}
</style>
</head>
<body>
<div id="controls">
<button id="play">播放</button>
<button id="pause">暂停</button>
<button id="reset">重置</button>
</div>
<div id="map"></div>
<script src="https://unpkg.com/three@0.126.0/build/three.min.js"></script>
<script src="https://unpkg.com/three@0.126.0/examples/js/loaders/GLTFLoader.js"></script>
<script>
mapboxgl.accessToken = 'pk.eyJ1Ijoiaml5ZXdlbiIsImEiOiJjbG5qeW9nZTIxbmtlMm1ybHVzMW04djh5In0._6aML1rN-1KVGurR9aIVCg';
const map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/light-v11',
zoom: 13,
center: [114.254338, 30.521912],
});
const modelOrigin = [114.254338, 30.521912];
const modelAltitude = 0;
const modelRotate = [Math.PI / 2, 0, 0];
const modelAsMercatorCoordinate = mapboxgl.MercatorCoordinate.fromLngLat(
modelOrigin,
modelAltitude
);
const modelTransform = {
translateX: modelAsMercatorCoordinate.x,
translateY: modelAsMercatorCoordinate.y,
translateZ: modelAsMercatorCoordinate.z,
rotateX: modelRotate[0],
rotateY: modelRotate[1],
rotateZ: modelRotate[2],
scale: modelAsMercatorCoordinate.meterInMercatorCoordinateUnits()
};
let trajectory = [
[114.246563, 30.512626],
[114.246588, 30.512679],
[114.246609, 30.512735]
];
let currentStep = 0;
let animationFrameId;
let isPlaying = false;
const THREE = window.THREE;
const customLayer = {
id: '3d-model',
type: 'custom',
renderingMode: '3d',
onAdd: function (map, gl) {
this.camera = new THREE.Camera();
this.scene = new THREE.Scene();
const directionalLight = new THREE.DirectionalLight(0xffffff);
directionalLight.position.set(0, -70, 100).normalize();
this.scene.add(directionalLight);
const directionalLight2 = new THREE.DirectionalLight(0xffffff);
directionalLight2.position.set(0, 70, 100).normalize();
this.scene.add(directionalLight2);
const loader = new THREE.GLTFLoader();
loader.load(
'https://docs.mapbox.com/mapbox-gl-js/assets/34M_17/34M_17.gltf',
(gltf) => {
this.model = gltf.scene;
this.scene.add(this.model);
}
);
this.map = map;
this.renderer = new THREE.WebGLRenderer({
canvas: map.getCanvas(),
context: gl,
antialias: true
});
this.renderer.autoClear = false;
},
moveModelTo: function (targetPosition) {
const duration = 1000;
const steps = 60;
const stepTime = duration / steps;
const originalPosition = {
x: modelTransform.translateX,
y: modelTransform.translateY,
z: modelTransform.translateZ
};
let stepCount = 0;
const animate = () => {
if (stepCount < steps && isPlaying) {
const progress = stepCount / steps;
modelTransform.translateX = originalPosition.x + (targetPosition.x - originalPosition.x) * progress;
modelTransform.translateY = originalPosition.y + (targetPosition.y - originalPosition.y) * progress;
modelTransform.translateZ = originalPosition.z + (targetPosition.z - originalPosition.z) * progress;
stepCount++;
requestAnimationFrame(animate);
}
};
animate();
},
render: function (gl, matrix) {
const rotationX = new THREE.Matrix4().makeRotationAxis(
new THREE.Vector3(1, 0, 0),
modelTransform.rotateX
);
const rotationY = new THREE.Matrix4().makeRotationAxis(
new THREE.Vector3(0, 1, 0),
modelTransform.rotateY
);
const rotationZ = new THREE.Matrix4().makeRotationAxis(
new THREE.Vector3(0, 0, 1),
modelTransform.rotateZ
);
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(rotationX)
.multiply(rotationY)
.multiply(rotationZ);
this.camera.projectionMatrix = m.multiply(l);
this.renderer.resetState();
this.renderer.render(this.scene, this.camera);
this.map.triggerRepaint();
}
};
function updateModelPosition() {
if (isPlaying && currentStep < trajectory.length) {
const lngLat = trajectory[currentStep];
const targetMercatorCoordinate = mapboxgl.MercatorCoordinate.fromLngLat(lngLat, modelAltitude);
modelTransform.translateX = targetMercatorCoordinate.x;
modelTransform.translateY = targetMercatorCoordinate.y;
modelTransform.translateZ = targetMercatorCoordinate.z;
currentStep++;
} else {
isPlaying = false;
cancelAnimationFrame(animationFrameId);
}
animationFrameId = requestAnimationFrame(updateModelPosition);
}
document.getElementById('play').onclick = function () {
if (!isPlaying) {
isPlaying = true;
updateModelPosition();
currentStep = currentStep < trajectory.length ? currentStep : 0;
}
};
document.getElementById('pause').onclick = function () {
isPlaying = false;
cancelAnimationFrame(animationFrameId);
};
document.getElementById('reset').onclick = function () {
isPlaying = false;
cancelAnimationFrame(animationFrameId);
currentStep = 0;
const startLngLat = trajectory[0];
const targetMercatorCoordinate = mapboxgl.MercatorCoordinate.fromLngLat(startLngLat, modelAltitude);
modelTransform.translateX = targetMercatorCoordinate.x;
modelTransform.translateY = targetMercatorCoordinate.y;
modelTransform.translateZ = targetMercatorCoordinate.z;
};
map.on('style.load', () => {
map.addLayer(customLayer, 'waterway-label');
});
</script>
</body>
</html>
import React, { useEffect, useRef, useState } from 'react';
import mapboxgl from 'mapbox-gl';
import * as THREE from 'three';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
import { getDistance2point } from '@/utils/common'
mapboxgl.accessToken = 'pk.eyJ1Ijoiaml5ZXdlbiIsImEiOiJjbG5qeW9nZTIxbmtlMm1ybHVzMW04djh5In0._6aML1rN-1KVGurR9aIVCg';
const App: React.FC = () => {
const mapRef = useRef<mapboxgl.Map | null>(null);
const isPlaying = useRef(false)
const currentStep = useRef(0)
const animationFrameId = useRef<number | null>(null)
const trajectory: [number, number][] = [
[
110.17113,
20.051918
],
[
110.171129,
20.051918
],
[
110.171128,
20.051919
],
[
110.171128,
20.051918
],
[
110.171128,
20.051919
],
[
110.171128,
20.051918
],
[
110.171128,
20.051919
],
[
110.171126,
20.05192
],
[
110.171124,
20.05192
],
[
110.171122,
20.051919
],
[
110.17112,
20.05192
],
[
110.171118,
20.051919
],
[
110.171117,
20.05192
],
[
110.171115,
20.051923
],
[
110.171107,
20.051931
],
[
110.1711,
20.051934
],
[
110.171086,
20.051936
],
[
110.17107,
20.051931
],
[
110.171059,
20.051924
],
[
110.171056,
20.051904
],
[
110.171041,
20.051891
],
[
110.171013,
20.051862
],
[
110.170985,
20.051836
],
[
110.170951,
20.051807
],
[
110.170915,
20.051773
],
[
110.170884,
20.051745
],
[
110.170838,
20.0517
],
[
110.170799,
20.051662
],
[
110.170761,
20.051623
],
[
110.170723,
20.051586
],
[
110.170691,
20.05156
],
[
110.170651,
20.051535
],
[
110.170601,
20.051521
],
[
110.170561,
20.051521
],
[
110.170541,
20.051526
],
[
110.170521,
20.051518
],
[
110.170494,
20.051493
],
[
110.170444,
20.051471
],
[
110.170448,
20.051425
],
[
110.170492,
20.051374
],
[
110.170477,
20.051342
],
[
110.170468,
20.051282
],
[
110.170452,
20.051249
],
[
110.170446,
20.05123
],
[
110.17044,
20.051223
],
[
110.170437,
20.051222
],
[
110.170437,
20.05122
],
[
110.170436,
20.051219
],
[
110.170435,
20.051214
],
[
110.170433,
20.051195
],
[
110.170434,
20.051172
],
[
110.170439,
20.051149
],
[
110.170453,
20.051119
],
[
110.170475,
20.051091
],
[
110.170506,
20.051066
],
[
110.170548,
20.051048
],
[
110.170599,
20.051037
],
[
110.170673,
20.051036
],
[
110.170729,
20.051037
],
[
110.170806,
20.051037
],
[
110.170906,
20.051036
],
[
110.170993,
20.051032
],
[
110.171063,
20.05103
],
[
110.171151,
20.051028
],
[
110.171241,
20.051028
],
[
110.171333,
20.05103
],
[
110.17144,
20.051032
],
[
110.17151,
20.051032
],
[
110.171596,
20.051034
],
[
110.171682,
20.051036
],
[
110.171783,
20.051041
],
[
110.171865,
20.051044
],
[
110.171931,
20.051047
],
[
110.172017,
20.051048
],
[
110.17212,
20.051047
],
[
110.172207,
20.051045
],
[
110.172273,
20.051044
],
[
110.172355,
20.051043
],
[
110.172438,
20.051039
],
[
110.172538,
20.051037
],
[
110.172587,
20.051036
],
[
110.172693,
20.051037
],
[
110.17275,
20.051038
],
[
110.172822,
20.051039
],
[
110.172906,
20.051038
],
[
110.172973,
20.051039
],
[
110.173036,
20.05104
],
[
110.173086,
20.051042
],
[
110.173146,
20.051043
],
[
110.17322,
20.051042
],
[
110.173284,
20.051041
],
[
110.173349,
20.051041
],
[
110.173412,
20.051041
],
[
110.173462,
20.051041
],
[
110.173538,
20.051042
],
[
110.1736,
20.051043
],
[
110.17366,
20.051043
],
[
110.173702,
20.051044
],
[
110.173749,
20.051044
],
[
110.173802,
20.051045
],
[
110.173841,
20.051045
],
[
110.173875,
20.051047
],
[
110.173906,
20.051046
],
[
110.173952,
20.051047
],
[
110.174004,
20.051049
],
[
110.174038,
20.051051
],
[
110.174141,
20.051048
],
[
110.1742,
20.051047
],
[
110.17428,
20.051045
],
[
110.174383,
20.051043
],
[
110.174471,
20.051043
],
[
110.17456,
20.051043
],
[
110.174647,
20.051044
],
[
110.174728,
20.051046
],
[
110.174788,
20.051048
],
[
110.174868,
20.051052
],
[
110.174925,
20.051055
],
[
110.174973,
20.051057
],
[
110.175007,
20.051059
],
[
110.175056,
20.051059
],
[
110.175091,
20.05106
],
[
110.175145,
20.051064
],
[
110.175192,
20.051076
],
[
110.175239,
20.051097
],
[
110.175273,
20.051122
],
[
110.175308,
20.051165
],
[
110.175334,
20.051234
],
[
110.175338,
20.051305
],
[
110.175334,
20.051381
],
[
110.175329,
20.051461
],
[
110.175327,
20.051538
],
[
110.175325,
20.05161
],
[
110.175324,
20.051677
],
[
110.175323,
20.05174
],
[
110.175322,
20.051791
],
[
110.175322,
20.051873
],
[
110.175322,
20.051948
],
[
110.175322,
20.052014
],
[
110.175324,
20.052101
],
[
110.175326,
20.052209
],
[
110.175326,
20.0523
],
[
110.175327,
20.05239
],
[
110.175326,
20.052462
],
[
110.175326,
20.052552
],
[
110.175326,
20.052659
],
[
110.175326,
20.052745
],
[
110.175327,
20.052825
],
[
110.175328,
20.052897
],
[
110.175329,
20.052952
],
[
110.175329,
20.053031
],
[
110.175328,
20.053096
],
[
110.175329,
20.05315
],
[
110.17533,
20.053236
],
[
110.175331,
20.053311
],
[
110.175332,
20.053376
],
[
110.175334,
20.053482
],
[
110.175334,
20.05354
],
[
110.175336,
20.053686
],
[
110.175342,
20.053794
],
[
110.175346,
20.053884
],
[
110.175347,
20.054022
],
[
110.175341,
20.054142
],
[
110.175335,
20.054241
],
[
110.175328,
20.054367
],
[
110.175321,
20.054522
],
[
110.175318,
20.054653
],
[
110.175316,
20.054761
],
[
110.175315,
20.054893
],
[
110.175314,
20.055049
],
[
110.175314,
20.055126
],
[
110.175313,
20.0553
],
[
110.175313,
20.055397
],
[
110.175313,
20.055543
],
[
110.175315,
20.055661
],
[
110.175317,
20.055776
],
[
110.175321,
20.055888
],
[
110.175327,
20.055997
],
[
110.175328,
20.056081
],
[
110.175324,
20.056185
],
[
110.175319,
20.056286
],
[
110.175315,
20.056402
],
[
110.175313,
20.056486
],
[
110.175312,
20.056537
],
[
110.175312,
20.056578
],
[
110.175312,
20.056598
],
[
110.175312,
20.0566
],
[
110.175312,
20.056598
],
[
110.175312,
20.056601
],
[
110.175312,
20.056618
],
[
110.175315,
20.056651
],
[
110.175325,
20.056685
],
[
110.175354,
20.05673
],
[
110.175403,
20.056769
],
[
110.175488,
20.0568
],
[
110.175575,
20.056813
],
[
110.175675,
20.056816
],
[
110.175762,
20.056818
],
[
110.175878,
20.056818
],
[
110.176027,
20.056815
],
[
110.176156,
20.056814
],
[
110.176262,
20.056815
],
[
110.176399,
20.056817
],
[
110.17654,
20.056818
],
[
110.176714,
20.056818
],
[
110.176862,
20.056817
],
[
110.177011,
20.056817
],
[
110.17716,
20.056818
],
[
110.17731,
20.056819
],
[
110.177429,
20.056819
],
[
110.177579,
20.056819
],
[
110.17773,
20.056819
],
[
110.177882,
20.056819
],
[
110.178063,
20.056819
],
[
110.178215,
20.05682
],
[
110.178337,
20.056821
],
[
110.178491,
20.056823
],
[
110.178648,
20.056824
],
[
110.178839,
20.056826
],
[
110.179,
20.056826
],
[
110.179131,
20.056827
],
[
110.179299,
20.056827
],
[
110.179469,
20.056827
],
[
110.179607,
20.056828
],
[
110.179854,
20.056829
],
[
110.180032,
20.056828
],
[
110.180175,
20.056828
],
[
110.180391,
20.056829
],
[
110.180567,
20.05683
],
[
110.18074,
20.05683
],
[
110.180909,
20.05683
],
[
110.181041,
20.056831
],
[
110.181234,
20.056833
],
[
110.181389,
20.056835
],
[
110.181536,
20.056836
],
[
110.181649,
20.056836
],
[
110.181783,
20.056835
],
[
110.181931,
20.056834
],
[
110.182044,
20.056834
],
[
110.182145,
20.056839
],
[
110.182215,
20.056843
],
[
110.182345,
20.056848
],
[
110.182361,
20.056848
],
[
110.182366,
20.056848
],
[
110.182366,
20.056849
],
[
110.182368,
20.056849
],
[
110.182369,
20.056848
],
[
110.18237,
20.056848
],
[
110.182372,
20.056847
],
[
110.182374,
20.056847
],
[
110.182383,
20.056847
],
[
110.182399,
20.056846
],
[
110.182435,
20.056846
],
[
110.182499,
20.056845
]
]
const modelOrigin = [110.17113,
20.051918];
const modelScale = 2;
const modelAltitude = 0;
const modelRotate = [Math.PI / 2, 0, 0];
const modelAsMercatorCoordinate = mapboxgl.MercatorCoordinate.fromLngLat(
modelOrigin,
modelAltitude
);
const modelTransform = {
translateX: modelAsMercatorCoordinate.x,
translateY: modelAsMercatorCoordinate.y,
translateZ: modelAsMercatorCoordinate.z,
rotateX: modelRotate[0],
rotateY: modelRotate[1],
rotateZ: modelRotate[2],
scale: modelAsMercatorCoordinate.meterInMercatorCoordinateUnits() * modelScale
};
const centerOffset = new THREE.Vector3();
useEffect(() => {
mapboxgl.accessToken = 'pk.eyJ1Ijoiaml5ZXdlbiIsImEiOiJjbG5qeW9nZTIxbmtlMm1ybHVzMW04djh5In0._6aML1rN-1KVGurR9aIVCg';
const map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/light-v11',
zoom: 15,
center: modelOrigin,
});
const customLayer = {
id: '3d-model',
type: 'custom',
renderingMode: '3d',
onAdd: function (map, gl) {
this.camera = new THREE.Camera();
this.scene = new THREE.Scene();
const frontLight = new THREE.DirectionalLight(0xffffff)
frontLight.position.set(1, 0, 0)
this.scene.add(frontLight)
const backLight = new THREE.DirectionalLight(0xffffff)
backLight.position.set(-1, 0, 0)
this.scene.add(backLight)
const leftLight = new THREE.DirectionalLight(0xffffff)
leftLight.position.set(0, 1, 0)
this.scene.add(leftLight)
const rightLight = new THREE.DirectionalLight(0xffffff)
rightLight.position.set(0, -1, 0)
this.scene.add(rightLight)
const loader = new GLTFLoader();
loader.load(
'Alpha5000.glb',
(gltf) => {
this.model = gltf.scene;
this.model.scale.set(modelScale, modelScale, modelScale);
this.scene.add(this.model);
const box = new THREE.Box3().setFromObject(this.model);
const center = box.getCenter(new THREE.Vector3());
centerOffset.copy(center).multiplyScalar(-1);
this.model.position.add(centerOffset);
}
);
this.map = map;
this.renderer = new THREE.WebGLRenderer({
canvas: map.getCanvas(),
context: gl,
antialias: true
});
this.renderer.autoClear = false;
},
render: function (gl: WebGLRenderingContext, matrix: number[]) {
const rotationX = new THREE.Matrix4().makeRotationAxis(
new THREE.Vector3(1, 0, 0),
modelTransform.rotateX
);
const rotationY = new THREE.Matrix4().makeRotationAxis(
new THREE.Vector3(0, 1, 0),
modelTransform.rotateY
);
const rotationZ = new THREE.Matrix4().makeRotationAxis(
new THREE.Vector3(0, 0, 1),
modelTransform.rotateZ
);
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(rotationX)
.multiply(rotationY)
.multiply(rotationZ);
this.camera.projectionMatrix = m.multiply(l);
this.renderer.resetState();
this.renderer.render(this.scene, this.camera);
this.map.triggerRepaint();
}
};
map.on('style.load', () => {
map.addLayer(customLayer, 'waterway-label');
});
mapRef.current = map
return () => {
map.remove();
};
}, [])
function needRotate(coord, coord1) {
return getDistance2point(coord, coord1) > 0.0002
}
function updateModelPosition() {
if (isPlaying.current && currentStep.current < trajectory.length) {
const lngLat = trajectory[currentStep.current];
const targetMercatorCoordinate = mapboxgl.MercatorCoordinate.fromLngLat(lngLat, modelAltitude);
modelTransform.translateX = targetMercatorCoordinate.x;
modelTransform.translateY = targetMercatorCoordinate.y;
modelTransform.translateZ = targetMercatorCoordinate.z;
const nextLngLat = trajectory[currentStep.current + 1];
if (nextLngLat && needRotate(lngLat, nextLngLat) && currentStep.current < trajectory.length - 1) {
const deltaLng = nextLngLat[0] - lngLat[0];
const deltaLat = nextLngLat[1] - lngLat[1];
const angle = Math.atan2(deltaLat, deltaLng);
modelTransform.rotateY = angle + 2 * Math.PI / 4;
}
currentStep.current++;
} else {
isPlaying.current = false;
cancelAnimationFrame(animationFrameId.current);
}
animationFrameId.current = requestAnimationFrame(updateModelPosition);
}
const handlePlay = () => {
if (!isPlaying.current) {
isPlaying.current = true;
updateModelPosition();
currentStep.current = currentStep.current < trajectory.length ? currentStep.current : 0;
}
};
const handlePause = () => {
isPlaying.current = false;
cancelAnimationFrame(animationFrameId.current);
};
const handleReset = () => {
isPlaying.current = false;
cancelAnimationFrame(animationFrameId.current);
currentStep.current = 0;
const startLngLat = trajectory[0];
const targetMercatorCoordinate = mapboxgl.MercatorCoordinate.fromLngLat(startLngLat, modelAltitude);
modelTransform.translateX = targetMercatorCoordinate.x;
modelTransform.translateY = targetMercatorCoordinate.y;
modelTransform.translateZ = targetMercatorCoordinate.z;
};
return (
<div>
<div id="controls" style={{ position: 'fixed', zIndex: 2, top: '10px', left: '10px', background: 'white', padding: '10px', borderRadius: '5px' }}>
<button onClick={handlePlay}>播放</button>
<button onClick={handlePause}>暂停</button>
<button onClick={handleReset}>重置</button>
</div>
<div id='map' style={{ position: 'absolute', top: 0, bottom: 0, width: '100%' }} />
</div>
);
};
export default App;