Creating 3D Interactive Earth using ThreeJS for web

Sreehari S
8 min readJul 15, 2023

--

I think it’s going to be long, so without wasting your time, I am going to explain the step-by-step procedures for creating an interactive 3D Earth using the Three.js library. First of all, apologies for the way I wrote the code for this project because this is my first JavaScript project, and I believe it’s a bit cluttered. Before we proceed any further, I recommend you to read the Three.js documentation to understand the basics. It’s an absolute treasure, and you can access it from here.

So I would like to divide the entire project into three part, 1.) Creating the 3D Earth & Controls, 2.) Creating the 3D terrain overlay 3.) Animation and other features. All the textures and assets used in this project can be found at this link and the live demo of this web app is available at this link (No proper touch device support yet). So lets build it.

  1. Creating the 3D Earth& Controls

So first step is to create the html file to place all the things that we are going to build and import the required dependencies.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>3D-Earth-ThreeJS-Tutorial</title>
<link rel="stylesheet" type="text/css" href="styles.css">
<style>
body { margin: 0; }
</style>

<script async src="https://unpkg.com/es-module-shims@1.6.3/dist/es-module-shims.js"></script>

<script type="importmap">
{
"imports": {
"three": "https://unpkg.com/three@0.154.0/build/three.module.js",
"three/addons/": "https://unpkg.com/three@0.154.0/examples/jsm/"
}
}
</script>

</head>
<body>
<div class="header">
<h1>3D Earth</h1>
<p1 id="displayText">Sample</p1>
<p2 id="cordText">Right Click for Coordinates</p2>
</div>

<div id="viz"></div>

<script type="module" src="./main.js"></script>



</body>
</html>

I’m not going into the whole detail of this part because it is simple and straightforward. There is a wonderful tutorial available at this link for this. However, I will just go through the steps involved in this section. To show a 3D model using Three.js, we require a few things: the geometry of the object, its material, the mesh, camera, light, scene, and the renderer for rendering the model to the screen.

In this case, the geometry is a sphere (sphere1) with a radius of 6378 units (the radius of the Earth), and the textures for the material can be found at this link. Once the geometry and material are ready, both are added to the mesh, and then it is added to the scene. Later, our camera is also positioned, and the light is set up to see things properly. Finally, the renderer is called to display the model. That’s it! You can see your 3D Earth on the screen.

//main.js

let earthRadius= 6378; //radius of sphere1(Earth)

//setting up earth(globe)
const mainSphere= new THREE.SphereGeometry(earthRadius,64,64); //creating geometry
const textureimg= new THREE.TextureLoader().load('textures/earth.jpg');
const texturebump= new THREE.TextureLoader().load('textures/bump.jpg');
const texturespec= new THREE.TextureLoader().load('textures/spec.jpg')
var material= new THREE.MeshPhongMaterial({ map:textureimg, bumpMap:texturebump, specularMap:texturespec }); //creating material& adding texture

var earthmesh= new THREE.Mesh(mainSphere, material); //adding geometry and material to mesh

// Setup renderer
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.getElementById('viz').appendChild(renderer.domElement);

// Setup scene
const scene = new THREE.Scene();
scene.add(new THREE.AmbientLight(0xbbbbbb));
scene.add(new THREE.DirectionalLight(0xffffff, 0.6));

scene.add(earthmesh);

//setting camera
const camera = new THREE.PerspectiveCamera(75,window.innerWidth / window.innerHeight,1,20000);
camera.position.set(1425,8000,-6160); //This is for demo
camera.lookAt(0,0,0);
camera.updateProjectionMatrix();

function animate(){
camera.updateProjectionMatrix();
renderer.render(scene, camera);
requestAnimationFrame(animate);
}

animate();

But how do you control it using your mouse? For that, add the code below.

// Add camera controls
var controls= new OrbitControls(camera, renderer.domElement);
controls.target.set(0,0,0);
controls.minDistance=6385;
controls.maxDistance=40000;
controls.enableDamping=true;
controls.dampingFactor=0.05;
controls.mouseButtons = {
LEFT: THREE.MOUSE.ROTATE,
MIDDLE: THREE.MOUSE.DOLLY,
RIGHT: THREE.MOUSE.PAN
};

//add controls.update() inside animate() function

Once this code is written, call the controls.update() method inside the animate function that we created previously.

2. Creating 3D terrain overlay

This is the interesting part in this tutorial and here we are going to make the 3D terrain and for that we will go through few Geographic Information System (GIS) terminology. So for creating the 3D terrain we need the Digital Elevation Model (DEM) of the region and a satellite RGB imagery (Landsat8 data used here) of same area to overlay the DEM. Here for the ease we converted the DEM and satellite imagery into png format. So here comes the most important question, that how we will overlay this images to the 3D model earth that we built initially? And how we will create 3D surface on the geometry?

Sphere1 is the Earth 3D model we created initially and Sphere2 is slice of sphere that we going to create (explained in next paragraph) to overlay the terrain.

So lets understand how the overlay of these images works. So every satellite images need their respective geographic coordinates to overlay on the Earth model. So in this case we have our geographic coordinates with us and it is in the form of the bounding box means geographic coordinate value of top-left corner of the image and bottom right corner of the image and from this values we can construct the height and width at the image situate on the Earth. But our 3D Earth model don’t understand geographic coordinates, so what we we will do now? Don’t worry, for that we have the phistart, philength, thetastart and thetalength property of ThreeJS SphereGeometry class and read its documentation thoroughly from here. Every Sphere in ThreeJS is having an horizontal sweep angle of 0 to 2Pi from left to right surrounding its vertical axis and vertical sweep angle of 0 to Pi from top to bottom sorrounding the horizontal plane and we assume these angles as the geographic coordinate system such a way that the left to right 0 to 2Pi is total 360 which is equal to -180 to 180 longitude and top to bottom 0 to Pi is total 180 which is equal to the 90 to -90 latitudes. So that’s it, we have figured out the geographic coordinate system and the mechanism behind that and what left is to overlay the image. So for that create a sphere geometry (sphere2) with radius exact of the Earth model and the phistart, thetastart, philength and thetastart is calculated from the bounding box and is used, then create a new material and then add the DEM as displacement map which creates the 3D terrain surface and add the landsat image as texture. Once the texture and geometry is created, create a new mesh out of these and add it to the same scene that we created for our first 3D Earth model. That’s it and the code for creating the terrain overlay is given below.

//create a second js file (overlay.js)

let bbox=[76.8485369367407429,9.7317189673059890,77.1207264675680193,10.1515915309470532] //bbox of part of idukki district


//adjusting the phistart and thetastart with respect to the lat-long system
//This is because longitude starts at an offset of 180 degs wrt phistart of ThreeJS spheregeometry and latitude has an offset of -90degs
var ps=180+parseFloat(bbox[0]) //phistart (ps)
var ts=90-parseFloat(bbox[3]) //thetastart (ts)

var pl= bbox[0]-bbox[2]; //philength (pl)
var tl= bbox[1]-bbox[3]; //thetalength (tl)

//function to check and remove negative value from bbox values
//This is to avoid neg lengths while substracting coordinates
function checkNeg(n){
if(n<0){
n=0-n;
}
return n;
}

for(let i=0;i<bbox.length;i++){
bbox[i]=checkNeg(bbox[i]);
}

var pl= bbox[0]-bbox[2]; //philength (pl)
var tl= bbox[1]-bbox[3]; //thetalength (tl)

pl=checkNeg(pl);
tl=checkNeg(tl);

//converting ps, ts, pl and tl to radians
ps= ps*(Math.PI/180);
ts= ts*(Math.PI/180);
pl= pl*(Math.PI/180);
tl= tl*(Math.PI/180);

export function overlay(){

const geometry= new THREE.SphereGeometry(6378,64,64,ps,pl,ts,tl); //creating geometry of sphere2 (for overlay)
const distext= new THREE.TextureLoader().load('assets/dem_crop.png'); //loading DEM as displacementMap which makes the surface undulated based on pixel value of DEM
const textureimg= new THREE.TextureLoader().load('assets/l8idk22_crop.png'); //landsat8 image which overlays the undulated surface


//adding the textures to material
var material= new THREE.MeshPhongMaterial({ map:textureimg,transparent:true,displacementMap:distext,displacementScale:1}); //dispacementscale determines the level of undulation in the terrain

//creating mesh (from material and geometry)
var overlaymesh= new THREE.Mesh(geometry, material);


return overlaymesh; //This mesh is returned and added to the same scene of first sphere. so everything will come together now

}

/***
This overlaymesh is added to same scene of first sphere (in main.js)
***/

3. Animation and other features

So far we built all necessary things and lets finish this by adding few additional features and for that you need to understand one basic thing in ThreeJS, that is the coordinate system. In ThreeJS, all the 3D models are arranged in a cartesian coordinate system therefore all the measurements are taken relative to that coordinate reference system. But in few cases like finding the geographic coordinates on mouse click and all, the conversion is needed from cartesian coordinate to degree coordinate and vice versa in few other cases like navigating to a particular lat-long location which I am not going to cover here because it makes this tutorial lot confusing. But the code for those can be found at my github repo which i will give below.

So initially we can create the flying animation from sky to your targeted area like that in google earth and for that just follow the below code.

//add in main.js file

//creating camera moving animation to the target area
//locations are given in cartesian coordinates
//You can use degToCart() function to convert lat-long to cartesian coordinates
const startPosition= new THREE.Vector3(1425,9000,-6160);
const endPosition= new THREE.Vector3(1425,40,-6160);
const animDuration=5;

const mixer= new THREE.AnimationMixer(camera);
const track= new THREE.VectorKeyframeTrack('.position',[0,animDuration],[startPosition.x, startPosition.y, startPosition.z,
endPosition.x, endPosition.y, endPosition.z]);

const clip= new THREE.AnimationClip('CameraAnimation', animDuration,[track]);
const action= mixer.clipAction(clip);

action.setLoop(THREE.LoopOnce);
action.clampWhenFinished=true;
action.play();

// after this add mixer.update(0.025); in the animate() function

and now you want to see the height from which you are seeing the things and for that add the below code

displayElement.textContent =controls.getDistance()-6378+" KMs";

and finally add the below code to get coordinates of the position where you right clicked with the mouse.

//finding coordinates on right mouse click
const raycaster= new THREE.Raycaster();
var mouse= new THREE.Vector2;

function onMouseClick(event){
event.preventDefault();
mouse.x=(event.clientX/window.innerWidth)*2-1;
mouse.y=-(event.clientY/window.innerHeight)*2+1;

raycaster.setFromCamera(mouse,camera);
var intersects = raycaster.intersectObjects(scene.children, true);

if (intersects.length > 0) {
// Get the first intersected object and its position
var intersectedObject = intersects[0].object;
var intersectedPoint = intersects[0].point;

let degcord= cartToDeg(intersectedPoint);
cordElement.textContent=degcord[0]+", "+degcord[1];

}
}

/***
In this I have used the function cartToDeg() which converts the local
cartesian coordinates to degree coordinates (lat-long).
***/

Conclusion

Repo link of this project: https://github.com/sreekmtl/3D-Earth-ThreeJS-Tutorial-

Live site (Desktop only): https://sreekmtl.github.io/3D-Earth-ThreeJS-Tutorial-/

I don’t think this is the most elegant way to implement an interactive 3D Earth using Three.js. There are several improvements that can be made, including the way assets are loaded. For example, the DEM and Landsat image can be directly loaded as WMS layers using any map server. Also additional features like administrative boundaries and other layers can be loaded as geojson layers which all I covered in my this repo which is the extended version of this project (https://github.com/sreekmtl/3D-Earth-ThreeJS).

--

--

Sreehari S

Programmer, Geospatial technology enthusiast. Likes to discuss about Programming, Computers, GIS and Remote sensing.