No more words, first look at the overall effect, this paper mainly explains the earth implementation

Core requirements

  • The earth is translucent and you can see behind it
  • Dot-matrix global map
  • Generate corresponding columns according to the latitude and longitude of the data
  • The higher the value, the darker and longer the column is

Introduce Threejs and D3

<script src="https://cdn.staticfile.org/three.js/r125/three.min.js"></script>
<script src="https://d3js.org/d3.v6.js"></script>
<script src="https://unpkg.com/[email protected]/examples/js/controls/OrbitControls.js"></script>
<script src="https://unpkg.com/[email protected]/examples/js/utils/BufferGeometryUtils.js"></script>
Copy the code

Basic HTML structure

<div id="box" style="width: 100%; height: 100%">
  <canvas id="canvas" style="width: 100%; height: 100%" />
</div>
Copy the code

Define the necessary variables

const box = document.getElementById("box");
const canvas = document.getElementById("canvas");

let glRender; // WebGL renderer
let camera; / / the camera
let earthMesh; // Earth Mesh
let scene; // Scene, a large container, can be understood as the body in HTML
let meshGroup; // The container for all the Mesh will be placed in this container, which is convenient for us to manage
let controls; // Track controller, realize the overall scene control
Copy the code

Initialize related variables

// Create a WebGL renderer
glRender = new THREE.WebGLRenderer({ canvas, alpha: true });
glRender.setSize(canvas.clientWidth, canvas.clientHeight, false);
// Create a scene
scene = new THREE.Scene();

// Create camera
const fov = 45;
const aspect = canvas.clientWidth / canvas.clientHeight;
const near = 1;
const far = 4000;
camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
// Set an appropriate location
camera.position.z = 400;

// Track controller
controls = new THREE.OrbitControls(camera, canvas);
controls.target.set(0.0.0);

// Create a container
meshGroup = new THREE.Group();
// Add the container to the scene
scene.add(meshGroup);
Copy the code

To create the earth

// Create a sphere first
const globeRadius = 100; // Radius of the sphere
const globeSegments = 64; // The larger the number of balls, the smoother the performance consumption is
const geometry = new THREE.SphereGeometry(
  globeRadius,
  globeSegments,
  globeSegments
);
// Create a sphere material to give the sphere a texture
const material = new THREE.MeshBasicMaterial({
  transparent: true.// Set transparency
  opacity: 0.5./ / transparency
  color: 0x000000./ / color
});
// Generate a grid object for the earth
earthMesh = new THREE.Mesh(geometry, material);
// Add earth to the mesh container
meshGroup.add(earthMesh);
Copy the code

The basic elements are created, but the interface doesn’t show anything. We still need to render the scene

function screenRender(){
  glRender.render(scene, camera);
  controls.update();
  requestAnimationFrame(screenRender);
}

screenRender()
Copy the code

At this point you should see a round object, so let’s start making the dot-matrix map

Map the lattice Mercator projection using the drawing processing tool

We record the coordinates of all the dots on the map and save the final result in mappoints.js

export default {
  "points": [{"x": 1.5."y": 2031.5
    },
    {
      "x": 1.5."y": 2016.5},... ] }Copy the code

Create a dot matrix map

// Introduce bitmatrix data
import mapPoints from "./mapPoints.js";
/** * Generate the point world map method */
function createMapPoints() {
  // The basic material of the point.
  const material = new THREE.MeshBasicMaterial({
    color: "#AAA"});const sphere = [];
  // Loop over all points to map 2-d coordinates to 3-d coordinates
  for (let point of mapPoints.points) {
    const pos = convertFlatCoordsToSphereCoords(point.x, point.y);
    if (pos.x && pos.y && pos.z) {
      // Generate a lattice
      const pingGeometry = new THREE.SphereGeometry(0.4.5.5); pingGeometry.translate(pos.x, pos.y, pos.z); sphere.push(pingGeometry); }}// Merge all the mesh arrays to generate a mesh object
  const earthMapPoints = new THREE.Mesh(
    THREE.BufferGeometryUtils.mergeBufferGeometries(sphere),
    material
  );
  // Add to the mesh container
  meshGroup.add(earthMapPoints);
}

/** * we need to get an array of 2-dimensional points, loop over and convert each point to its 3-dimensional position. This is the function that performs the transformation. Depending on the size of the template projection you create, you may need to adjust the first few variables */
const globeWidth = 4098 / 2;
const globeHeight = 1968 / 2;

function convertFlatCoordsToSphereCoords(x, y) {
  let latitude = ((x - globeWidth) / globeWidth) * -180;
  let longitude = ((y - globeHeight) / globeHeight) * -90;
  latitude = (latitude * Math.PI) / 180;
  longitude = (longitude * Math.PI) / 180; 
  const radius = Math.cos(longitude) * globeRadius;
  const x = Math.cos(latitude) * radius;
  const y = Math.sin(longitude) * globeRadius;
  const z = Math.sin(latitude) * radius;
  return {
    x,
    y,
    z,
  };
}
Copy the code

Call the createMapPoints method after meshgroup.add (earthMesh)

.// Add earth to the mesh container
meshGroup.add(earthMesh);
// Create a bitmap
createMapPoints();

// Render the scenescreenRender(); .Copy the code

Beautiful!

Next, we generate columns and collect data fromdisease.shAnd convert it to a structure that is convenient for us to use and save it in data.js

// Convert good data
import data from "./data.js"
// Define the color mapping of the data, which can be dynamically calculated according to the actual data
const colors = ["#ffdfe0"."#ffc0c0"."#FF0000"."#ee7070"."#c80200"."# 900000"."# 510000"."# 290000"];
const domain = [1000.3000.10000.50000.100000.500000.1000000.1000000];
Copy the code

Creates the build cylinder method

function createBar() {
  if(! data || data.length ===0) return;

  let color;
  // d3 scale
  const scale = d3.scaleLinear().domain(domain).range(colors);

  // Loop over the data
  data.forEach(({ lat, lng, value: size }) = > {
    // Get the corresponding color of the data from the scale
    color = scale(size);
    const pos = convertLatLngToSphereCoords(lat, lng, globeRadius);
    if (pos.x && pos.y && pos.z) {
      // We use cubes to generate bar charts
      const geometry = new THREE.BoxGeometry(2.2.1);
      // Move cube Z so that it stands on the earth's surface
      geometry.applyMatrix4(
        new THREE.Matrix4().makeTranslation(0.0, -0.5));// Generate a cube mesh
      const barMesh = new THREE.Mesh(
        geometry,
        new THREE.MeshBasicMaterial({
          color,
        })
      );
      // Set the location
      barMesh.position.set(pos.x, pos.y, pos.z);
      // Set the orientation
      barMesh.lookAt(earthMesh.position);

      // Set the column length according to the data, except 20000. In order to prevent the column length is too long, you can adjust according to the actual situation, or make a parameter
      barMesh.scale.z = Math.max(size/20000.0.1);
      barMesh.updateMatrix();
      // Add to the mesh containermeshGroup.add(barMesh); }}); }// Latitude and longitude are changed to spherical coordinates
  function convertLatLngToSphereCoords(latitude, longitude, radius) {
    const phi = (latitude * Math.PI) / 180;
    const theta = ((longitude - 180) * Math.PI) / 180;
    const x = -(radius + -1) * Math.cos(phi) * Math.cos(theta);
    const y = (radius + -1) * Math.sin(phi);
    const z = (radius + -1) * Math.cos(phi) * Math.sin(theta);
    return {
      x,
      y,
      z,
    };
  }
Copy the code

Call createBar() after createMapPoints()

.// Call the createMapPoints method after this method
meshGroup.add(earthMesh);
// Create a bitmap
createMapPoints();
// Create a bar chart
createBar()

// Render the scenescreenRender(); .Copy the code

The final result

Tutorial complete code:Github.com/drinkjs/Ear…

Overall effect code:Github.com/drinkjs/moj…