# 北京到上海，Three.js 旅行轨迹的可视化

9,186

## 思路分析

Three.js 画立方体、画圆柱、画不规则图形我们都画过，但是如何画一个地图呢？

geojson 的数据可以通过 geojson.io 这个网站做下预览。

## 代码实现

``````const projection = d3.geoMercator()
.center([116.412318,39.909843])
.translate([0, 0]);
``````

``````let beijingPosition= projection([116.412318,39.909843]);
let shanghaiPosition = projection([121.495721,31.236797]);
``````

``````const loader = new THREE.FileLoader();
const jsondata = JSON.parse(data);
generateGeometry(jsondata);
})
``````

``````function generateGeometry(jsondata) {
const map = new THREE.Group();

jsondata.features.forEach((elem) => {
const province = new THREE.Group();

// 经纬度信息
const coordinates = elem.geometry.coordinates;
coordinates.forEach((multiPolygon) => {
multiPolygon.forEach((polygon) => {
// 画轮廓线
const line = drawBoundary(polygon);

// 画多边形
const provinceColor = ['北京市', '上海市'].includes(elem.properties.name) ? 'yellow' : 'blue';
const mesh = drawExtrudeMesh(polygon, provinceColor);

});
});

})

}
``````

``````function drawBoundary(polygon) {
const lineGeometry = new THREE.Geometry();

for (let i = 0; i < polygon.length; i++) {
const [x, y] = projection(polygon[i]);
lineGeometry.vertices.push(new THREE.Vector3(x, -y, 0));
}

const lineMaterial = new THREE.LineBasicMaterial({
color: 'yellow'
});

return new THREE.Line(lineGeometry, lineMaterial);
}
``````

``````function drawExtrudeMesh(polygon, color) {
const shape = new THREE.Shape();

for (let i = 0; i < polygon.length; i++) {
const [x, y] = projection(polygon[i]);

if (i === 0) {
shape.moveTo(x, -y);
}

shape.lineTo(x, -y);
}

const geometry = new THREE.ExtrudeGeometry(shape, {
depth: 0,
bevelEnabled: false
});

const material = new THREE.MeshBasicMaterial({
color,
transparent: true,
opacity: 0.2,
})

return new THREE.Mesh(geometry, material);
}
``````

``````const line = drawLine(beijingPosition, shanghaiPosition);
``````

``````function drawLine(pos1, pos2) {
const [x0, y0, z0] = [...pos1, 0];
const [x1, y1, z1] = [...pos2, 0];

const geomentry = new THREE.Geometry();
new THREE.Vector3(-x0, -y0, z0),
new THREE.Vector3(-(x0 + x1) / 2, -(y0 + y1) / 2, -10),
new THREE.Vector3(-x1, -y1, z1),
).getPoints();

const material = new THREE.LineBasicMaterial({color: 'white'});

const line = new THREE.Line(geomentry, material);
line.rotation.y = Math.PI;

return line;
}
``````

``````const renderer = new THREE.WebGLRenderer();
renderer.setClearColor(0x000000);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
``````

``````let ambientLight = new THREE.AmbientLight(0xffffff);
``````

``````const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 0, 10);
camera.lookAt(scene.position);
``````

``````function render() {
if(camera.position.x < shanghaiPosition[0]) {
camera.position.x += 0.1;
}
if(camera.position.y > -shanghaiPosition[1]) {
camera.position.y -= 0.2;
}
renderer.render(scene, camera);
requestAnimationFrame(render);
}
``````

``````<!DOCTYPE html>
<html lang="en">
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>map-travel</title>
<style>
html body {
height: 100%;
width: 100%;
margin: 0;
overflow: hidden;
}
</style>
<body>
<script src="./js/three.js"></script>
<script src="./js/d3.js"></script>
<script>
const scene = new THREE.Scene();

const renderer = new THREE.WebGLRenderer();
renderer.setClearColor(0x000000);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 0, 10);
camera.lookAt(scene.position);

let ambientLight = new THREE.AmbientLight(0xffffff);

function create() {
const jsondata = JSON.parse(data);
generateGeometry(jsondata);
})
}

const projection = d3.geoMercator()
.center([116.412318,39.909843])
.translate([0, 0]);

let beijingPosition= projection([116.412318,39.909843]);
let shanghaiPosition = projection([121.495721,31.236797]);

function drawBoundary(polygon) {
const lineGeometry = new THREE.Geometry();

for (let i = 0; i < polygon.length; i++) {
const [x, y] = projection(polygon[i]);
lineGeometry.vertices.push(new THREE.Vector3(x, -y, 0));
}

const lineMaterial = new THREE.LineBasicMaterial({
color: 'yellow'
});

return new THREE.Line(lineGeometry, lineMaterial);
}

function drawExtrudeMesh(polygon, color) {
const shape = new THREE.Shape();

for (let i = 0; i < polygon.length; i++) {
const [x, y] = projection(polygon[i]);

if (i === 0) {
shape.moveTo(x, -y);
}

shape.lineTo(x, -y);
}

const geometry = new THREE.ExtrudeGeometry(shape, {
depth: 0,
bevelEnabled: false
});

const material = new THREE.MeshBasicMaterial({
color,
transparent: true,
opacity: 0.2,
})

return new THREE.Mesh(geometry, material);
}

function generateGeometry(jsondata) {
const map = new THREE.Group();

jsondata.features.forEach((elem) => {
const province = new THREE.Group();

const coordinates = elem.geometry.coordinates;
coordinates.forEach((multiPolygon) => {
multiPolygon.forEach((polygon) => {
const line = drawBoundary(polygon);

const provinceColor = ['北京市', '上海市'].includes(elem.properties.name) ? 'yellow' : 'blue';
const mesh = drawExtrudeMesh(polygon, provinceColor);

});
});

})

const line = drawLine(beijingPosition, shanghaiPosition);

}

function render() {
if(camera.position.x < shanghaiPosition[0]) {
camera.position.x += 0.1;
}
if(camera.position.y > -shanghaiPosition[1]) {
camera.position.y -= 0.2;
}
renderer.render(scene, camera);
requestAnimationFrame(render);
}

function drawLine(pos1, pos2) {
const [x0, y0, z0] = [...pos1, 0];
const [x1, y1, z1] = [...pos2, 0];

const geomentry = new THREE.Geometry();
new THREE.Vector3(-x0, -y0, z0),
new THREE.Vector3(-(x0 + x1) / 2, -(y0 + y1) / 2, -10),
new THREE.Vector3(-x1, -y1, z1),
).getPoints();

const material = new THREE.LineBasicMaterial({color: 'white'});

const line = new THREE.Line(geomentry, material);
line.rotation.y = Math.PI;

return line;
}

create();
render();
</script>
</body>
</html>
``````