下午花了幾個小時邊學邊做,做出來了一箇中國地圖的擠壓模型。其中中國地圖的數據是geojson的格式,由於相關法律這裏無法提供地圖數據。如果想要學習交流使用可以前往github上翻一翻。
使用的工具很單純:
- THREE.js (ver 11.5,主要用了擠壓模型和緩衝模型,材質使用的基礎半透明材質和線材質)
- d3.v5.js (只用到了d3-geo的Mercator變換和其他的座標變換小工具)
參考:
- https://luosijie.github.io/threejs-examples/#/china-map
- https://github.com/d3/d3-geo/blob/v1.11.9/README.md#geoMercator
- https://threejs.org/docs/index.html#api/zh/geometries/ExtrudeGeometry
效果如下圖:
全部代碼先懶洋洋地堆在這兒,什麼時候想起來了就解釋一下↓
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>map-3d</title>
<style>
* {
margin: 0;
padding: 0;
}
body {
overflow: hidden;
}
</style>
</head>
<body>
<canvas id="main-canvas"></canvas>
<script src="../../../javascripts/three115.min.js"></script>
<script src="../../../javascripts/OrbitControls.js"></script>
<script src="../../../javascripts/d3.v5.min.js"></script>
<script>
let main_canvas;
let scene, camera, renderer;
let map;
function init(geo_data) {
console.log(geo_data);
main_canvas = document.querySelector("#main-canvas");
main_canvas.setAttribute("width", window.innerWidth);
main_canvas.setAttribute("height", window.innerHeight);
scene = new THREE.Scene();
scene.background = new THREE.Color("#01111A");
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight);
camera.position.set(0, 0, 80);
renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true, canvas: main_canvas });
// map
let map = new THREE.Object3D();
const projection = d3.geoMercator().center([104.0, 37.5]).scale(80).translate([0, 0]);
geo_data["features"].forEach((e) => {
const province = new THREE.Object3D();
const coors = e["geometry"]["coordinates"];
coors.forEach((multipolygon) => {
multipolygon.forEach((polygon) => {
const shape = new THREE.Shape();
const lineMaterial = new THREE.LineBasicMaterial({ color: "#FFF", linewidth: 1 });
const lineGeometry = new THREE.BufferGeometry();
let line_vertices = [];
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);
line_vertices.push(new THREE.Vector3(x, -y, 4.01));
}
lineGeometry.setFromPoints(line_vertices);
const extrudeSettings = {
depth: 4,
bevelEnabled: false,
};
const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings);
const material = new THREE.MeshBasicMaterial({ color: "#069EF2", transparent: true, opacity: 0.6 });
const mesh = new THREE.Mesh(geometry, material);
province.add(mesh);
const line = new THREE.Line(lineGeometry, lineMaterial);
province.add(line);
});
});
province.properties = e.properties;
if (e.properties.centroid) {
const [x, y] = projection(e.properties.centroid);
province.properties._centroid = [x, y];
}
map.add(province);
});
map.rotation.x = ((-Math.PI / 2) * 3) / 4;
scene.add(map);
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.minDistance = 1;
controls.maxDistance = 5000;
controls.maxPolarAngle = Math.PI * 2;
controls.minPolarAngle = 0;
controls.enabled = true;
}
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
function startup() {
fetch("../../../data/geodata/china_2.json")
.then((res) => res.json())
.then((data) => {
init(data);
animate();
});
}
startup();
</script>
</body>
</html>