一、数据准备
阿里云数据可视化平台获取行政区划边界数据:DataV.GeoAtlas地理小工具系列
下载区域经纬度json数据,在本地重命名 china.json 放置到 项目 dist/assets 目录下
二、依赖安装
# 安装 three.js
npm install three --save
# 安装 d3
npm install d3 --save
三、代码实现
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Three.js 3D地图绘制</title>
<link rel="stylesheet" href="./assets/css/style.css">
</head>
<body>
<script type="module" src="./main.js"></script>
</body>
</html>
main.js
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
import Stats from "three/examples/jsm/libs/stats.module.js";
import * as d3 from "d3";
// 渲染性能监测工具
const stats = new Stats();
document.body.appendChild(stats.dom);
// 初始化场景
const scene = new THREE.Scene();
// 创建透视相机
const camera = new THREE.PerspectiveCamera(
90,
window.innerHeight / window.innerHeight,
0.1,
100000
);
// 设置相机位置
camera.position.set(0, 0, 120);
// 更新摄像头
camera.aspect = window.innerWidth / window.innerHeight;
// 更新摄像机的投影矩阵
camera.updateProjectionMatrix();
scene.add(camera);
// 加入辅助轴,帮助我们查看3维坐标轴
const axesHelper = new THREE.AxesHelper(5);
scene.add(axesHelper);
// 添加环境光和直射光
const light = new THREE.AmbientLight(0xffffff, 1); // soft white light
scene.add(light);
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
scene.add(directionalLight);
// 初始化渲染器
const renderer = new THREE.WebGLRenderer({ alpha: true });
// 设置渲染尺寸大小
renderer.setSize(window.innerWidth, window.innerHeight);
// 将渲染器添加到body
document.body.appendChild(renderer.domElement);
// 初始化控制器
const controls = new OrbitControls(camera, renderer.domElement);
// 设置控制器阻尼
controls.enableDamping = true;
// 监听屏幕大小改变的变化,动态设置渲染的尺寸
window.addEventListener("resize", () => {
// 更新摄像头
camera.aspect = window.innerWidth / window.innerHeight;
// 更新摄像机的投影矩阵
camera.updateProjectionMatrix();
// 更新渲染器
renderer.setSize(window.innerWidth, window.innerHeight);
// 设置渲染器的像素比例
renderer.setPixelRatio(window.devicePixelRatio);
});
function animate() {
controls.update();
stats.update();
requestAnimationFrame(animate);
// 使用渲染器渲染相机看这个场景的内容渲染出来
renderer.render(scene, camera);
}
animate();
const canvas = renderer.domElement;
// 构造生成三维物体对象
const map = new THREE.Object3D();
// 地图geojson的json文件得到的坐标点是经纬度数据,需要把它转为坐标数据,
// 这里使用插件d3 geoMercator()方法转换
// .center: 以北京经纬度坐标(116.23, 39.54)为中心点
// .translate 移动地图位置
const projection = d3.geoMercator().center([116.23, 39.54]).translate([0, 0, 0]);
const fileLoader = new THREE.FileLoader();
fileLoader.load("./assets/china.json", (data) => {
const chinaJsonData = JSON.parse(data);
console.log("chinaJsonData: ", chinaJsonData);
handleData(chinaJsonData);
})
/**
* 处理地图数据
* @param {Object} jsonData
*/
function handleData(jsonData) {
// 全国信息
const features = jsonData.features;
features.forEach((feature) => {
console.log(feature);
// 单个省份 对象
const province = new THREE.Object3D();
// 地址
province.propertiesName = feature.properties.name;
const coordinates = feature.geometry.coordinates;
const color = "#99ff99";
if (feature.geometry.type === "MultiPolygon") {
// 多个,多边形
coordinates.forEach((coordinate) => {
// console.log(coordinate);
// coordinate 多边形数据
coordinate.forEach((coord) => {
const mesh = drawExtrudeMesh(coord, color, projection);
const line = drawLine(coord, color, projection);
// 唯一标识
mesh.name = feature.properties.name;
province.add(mesh);
province.add(line);
});
});
}
if (feature.geometry.type === "Polygon") {
// 多边形
coordinates.forEach((coordinate) => {
const mesh = drawExtrudeMesh(coordinate, color, projection);
const line = drawLine(coordinate, color, projection);
// 唯一标识
mesh.name = feature.properties.name;
province.add(mesh);
province.add(line);
});
}
map.add(province);
});
scene.add(map);
}
/**
* 根据经纬度坐标生成几何物体
* @param {Array} polygon 经纬度坐标数据
* @param {string} color 物体颜色
* @param {Function} projectionFun 经纬度转平面坐标函数
* @returns THREE.Mesh
*/
function drawExtrudeMesh(polygon, color, projectionFun) {
const shape = new THREE.Shape();
polygon.forEach((row, i) => {
const [x, y] = projectionFun(row);
if (i === 0) {
// 创建起点,使用moveTo方法
// 因为计算出来的y是反过来的,所以要进行颠倒
shape.moveTo(x, -y);
}
shape.lineTo(x, -y);
});
// 挤压缓冲几何体, 拉伸
const geometry = new THREE.ExtrudeGeometry(shape, {
depth: 5, // 挤出的形状的深度
bevelEnabled: true, // 对挤出的形状应用是否斜角
})
// 随机物体颜色或者使用传入的color,这里使用随机颜色
const randomColor = (Math.random() * 0.5 + 0.5) * 0xffffff;
const material = new THREE.MeshBasicMaterial({
color: randomColor,
transparent: true,
opacity: 0.9,
});
return new THREE.Mesh(geometry, material);
}
/**
* 根据坐标点一条连续的线
* @param {*} polygon 经纬度坐标数据
* @param {*} color 线的颜色
* @param {*} projectionFun 经纬度转平面坐标函数
* @returns THREE.Line
*/
function drawLine(polygon, color, projectionFun) {
const lineGeometry = new THREE.BufferGeometry();
const pointsArr = [];
polygon.forEach((row) => {
const [x, y] = projectionFun(row);
// 创建三维点
pointsArr.push(new THREE.Vector3(x, -y, 5.5));
});
// 放入多个点
lineGeometry.setFromPoints(pointsArr);
// 生成随机颜色
const lineColor = new THREE.Color(
Math.random() * 0.5 + 0.5,
Math.random() * 0.5 + 0.5,
Math.random() * 0.5 + 0.5
);
const lineMaterial = new THREE.LineBasicMaterial({
color: lineColor
});
return new THREE.Line(lineGeometry, lineMaterial);
}
// 上次点击选中的省份或直辖市
var lastPick = null;
window.addEventListener("click", handleByRay);
function handleByRay(event) {
// 获取鼠标位置
// const pickPosition = getPickPosition(event);
const mouse = new THREE.Vector2();
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -((event.clientY / window.innerHeight) * 2 - 1);
// 获取鼠标点击的位置
const raycaster = new THREE.Raycaster();
raycaster.setFromCamera(mouse, camera);
// 计算物体和射线的交点
const intersects = raycaster.intersectObjects(map.children, true);
// 数组大于0, 表示有相交对象
if (intersects.length > 0) {
if (lastPick) {
lastPick.object.material.color.copy(lastPick.object.material.originColor);
lastPick = null;
}
lastPick = intersects[0];
lastPick.object.material.originColor = lastPick.object.material.color.clone();
lastPick.object.material.color.set("#fff000");
} else {
if (lastPick) {
// 复原颜色
lastPick.object.material.color.copy(lastPick.object.material.originColor);
lastPick = null;
}
}
}
以上就是Three.js绘制3D中国地图的实现demo,可以根据自身需要进行颜色,标签,线条等相关修改