一、数据准备

阿里云数据可视化平台获取行政区划边界数据: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,可以根据自身需要进行颜色,标签,线条等相关修改

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐