THREE + d3製作中國地圖擠壓(extrude)模型

下午花了幾個小時邊學邊做,做出來了一箇中國地圖的擠壓模型。其中中國地圖的數據是geojson的格式,由於相關法律這裏無法提供地圖數據。如果想要學習交流使用可以前往github上翻一翻。

使用的工具很單純:

  • THREE.js (ver 11.5,主要用了擠壓模型和緩衝模型,材質使用的基礎半透明材質和線材質)
  • d3.v5.js (只用到了d3-geo的Mercator變換和其他的座標變換小工具)

參考:

效果如下圖:

全部代碼先懶洋洋地堆在這兒,什麼時候想起來了就解釋一下↓

<!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>

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章