Skip to content

一、threejs地图-绘制中国地图

1. 中国地图

  • 地图数据的获取 阿里提供的数据可视化平台

  • 导入数据,在geojson.io 上显示 china.png

  • 数据结构

    • 查看数据结构发现,算上九段线,有34MultiPolygon 和 1个Polygon
    • 观察其数据结构
      • MultiPolygon
        json5
        {
            coordinates: [
            [
              [
                ['经度', '纬度'],
                ['经度', '纬度'],
                ['经度', '纬度'],
              ]
            ],
            [
              [
                ['经度', '纬度'],
                ['经度', '纬度'],
                ['经度', '纬度'],
              ]
            ],
            [
              [
                ['经度', '纬度'],
                ['经度', '纬度'],
                ['经度', '纬度'],
              ]
            ]
         ]
        }
      • Polygon的数据结构
        json5
          {
           coordinates: [
              [
                ['经度', '纬度'],
                ['经度', '纬度'],
                ['经度', '纬度'],
              ]
            ]
          }
    • 查看地图的数据结构发现:
      • PolygonMultiPolygon 两者数据结构
      • MultiPolygoncoordinates节点的数据节点多一层

二、绘制中国地图

1. 核心代码

  • 数据解析
    js
      /**
       * 处理地图经纬度数据
       */
      initChinaMap() 
      {
        this.#mapData?.features?.map((item) => {
          const areas = item.geometry.coordinates;
          const type = item.geometry.type;
          // 将其他属性依旧放在 properties 中,将 coordinates 中的坐标转成 空间坐标
          const areaVector = { ...item.properties, coordinates: [] };
          areas.map((area, index) => {
            if (type === POLYGON) {
              areaVector.coordinates[index] = [];
              area.map((point) => {
                areaVector.coordinates[index].push(this.lnglatToVector(point))
              })
            } else if (type === MULTI_POLYGON) {
              areaVector.coordinates[index] = [];
              area.map((point) => {
                point?.map(pointInner => {
                  areaVector.coordinates[index].push(this.lnglatToVector(pointInner))
                })
              })
            }
          })
          this.#vector3Json.push(areaVector);
        })
        // 绘制模块
        const group = new Group();
        this.#vector3Json.map(vectors => {
          vectors.coordinates.map(vector => {
            const mesh = this.drawChina(vector);
            group.add(mesh);
          })
        })
        group.rotation.y = Math.PI;
        this.scene.add(group);
      }

2. 完整示例

  • 代码示例

    js
    import ThreeBase from "./ThreeBase";
    import * as D3 from 'd3-geo';
    import axios from "axios";
    import { Group, Shape, ExtrudeGeometry, MeshBasicMaterial, Mesh } from 'three';
    
    
    const MULTI_POLYGON  = 'MultiPolygon';
    const POLYGON = 'Polygon'
    /**
     * 绘制多多边形
     */
    export default class ThreeMultiplePolygonMap extends ThreeBase {
    
      #projection;
      #mapData;
      #vector3Json = [];
    
      constructor() {
        super();
    
      }
    
      initObj() {
        this.initMapData().then(() => {
          this.initChinaMap()
        })
      }
    
      initMapData() {
        return axios.get('geojson/china.json').then(res => {
          this.#mapData = res.data;
        })
      }
    
      /**
       * 绘制网格
       */
      drawChina(points= []) {
        const shape = new Shape();
        points?.map((point, index) => {
          const [x, y] = point;
          if (index === 0) {
            shape.moveTo(x, y);
          } else if (index === points.length - 1) {
            // 二次曲线
            shape.quadraticCurveTo(x, y, x, y)
          } else {
            shape.lineTo(x, y, x, y)
          }
        })
        // bevelEnabled 是否启用斜角
        const geometry = new ExtrudeGeometry(
          shape, {
            depth: -2, bevelEnabled: false
          }
        )
        // 材质
        const material = new MeshBasicMaterial({
          color: "#007cff",
          transparent: true,
          opacity: 0.5
        });
    
        // 合并
        const mesh = new Mesh(geometry, material);
        mesh.rotateX(Math.PI /2 )
        return mesh;
      }
    
      /**
       * 处理地图经纬度数据
       */
      initChinaMap() {
        this.#mapData?.features?.map((item) => {
          const areas = item.geometry.coordinates;
          const type = item.geometry.type;
          const areaVector = { ...item.properties, coordinates: [] };
          areas.map((area, index) => {
            if (type === POLYGON) {
              areaVector.coordinates[index] = [];
              area.map((point) => {
                areaVector.coordinates[index].push(this.lnglatToVector(point))
              })
            } else if (type === MULTI_POLYGON) {
              areaVector.coordinates[index] = [];
              area.map((point) => {
                point?.map(pointInner => {
                  areaVector.coordinates[index].push(this.lnglatToVector(pointInner))
                })
              })
            }
          })
          this.#vector3Json.push(areaVector);
        })
        // 绘制模块
        const group = new Group();
        this.#vector3Json.map(vectors => {
          vectors.coordinates.map(vector => {
            const mesh = this.drawChina(vector);
            group.add(mesh);
          })
        })
        group.rotation.y = Math.PI;
        this.scene.add(group);
      }
    
      lnglatToVector(lnglat) {
        if (!this.#projection) {
          this.#projection = D3.geoMercator()
            .center([112.946332, 28.236672])
            .scale(80)
            .translate([0,0]);
        }
        const [y, x] = this.#projection([...lnglat]);
        const z = 0;
        return [y, x, z];
      }
    }
  • 效果展示

    china-three.png

Released under the MIT License.