Interactive 3D Globe in Three.js
Interactive Three.js 3D globe visualization with geospatial location pins, latitude/longitude grid overlays, smooth auto-rotation, and configurable styling for geographic data storytelling and map-based dashboards.
Key topics: three.js globe, 3d globe visualization, interactive world map, geographic data visualization, three.js earth, 3d map pins, location markers, latitude longitude grid, rotating globe, world map three.js, data visualization globe, animated globe
Game Over
What this code does
- Three.js Globe Geometry: realistic Earth sphere (64x64 segments) with Phong material for smooth lighting and subtle ocean-blue coloring.
- Geographic Pin System: 10 major world cities (New York, London, Tokyo, Sydney, Mumbai, São Paulo, Cairo, Moscow, Singapore, Cape Town) marked with 3D pin markers.
- Latitude/Longitude Grid: mathematically generated wireframe grid lines overlay the globe showing 20° intervals for geographic reference.
- Pin Geometry: each pin consists of a cylindrical stick and spherical head, positioned using spherical coordinates converted from lat/lon.
- Animated Pins: pulsing animation on pin heads creates attention-grabbing effects synchronized with different phase offsets.
- Color-Coded Locations: each city pin has a unique vibrant color (red, green, blue, yellow, magenta, cyan, orange, purple) for easy differentiation.
- Auto-Rotation: smooth automatic globe rotation with orbit controls for manual inspection and exploration.
- Rim Lighting: directional lights from multiple angles create realistic globe illumination with atmospheric edge glow.
- Interactive Controls: real-time GUI adjustment of rotation speed, pin height, pulse animation, grid opacity, and globe color.
- Geographic Coordinate System: demonstrates conversion of latitude/longitude to 3D Cartesian coordinates using spherical math.
- Data Visualization Ready: easily extendable to display custom geographic datasets, population data, or real-time global statistics.
Related Demos
Continue Learning
JavaScript Implementation
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
import { CSS2DRenderer, CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer.js'
const scene = new THREE.Scene()
scene.background = new THREE.Color(0x0a0a0f)
const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000)
camera.position.set(0, 0, 3.5)
const renderer = new THREE.WebGLRenderer({ antialias: true })
renderer.setSize(window.innerWidth, window.innerHeight)
document.body.appendChild(renderer.domElement)
// CSS2D renderer for labels
const labelRenderer = new CSS2DRenderer()
labelRenderer.setSize(window.innerWidth, window.innerHeight)
labelRenderer.domElement.style.position = 'absolute'
labelRenderer.domElement.style.top = '0'
labelRenderer.domElement.style.pointerEvents = 'none'
document.body.appendChild(labelRenderer.domElement)
const controls = new OrbitControls(camera, renderer.domElement)
controls.enableDamping = true
controls.autoRotate = true
controls.autoRotateSpeed = 0.5
// Load Earth texture and create globe
const textureLoader = new THREE.TextureLoader()
const earthTexture = textureLoader.load(
'https://raw.githubusercontent.com/mrdoob/three.js/dev/examples/textures/planets/earth_atmos_2048.jpg'
)
const globe = new THREE.Mesh(
new THREE.SphereGeometry(1, 64, 64),
new THREE.MeshPhongMaterial({
map: earthTexture,
shininess: 15
})
)
scene.add(globe)
// Add grid lines (latitude)
const gridMaterial = new THREE.LineBasicMaterial({ color: 0x4da6ff, transparent: true, opacity: 0.3 })
for (let lat = -80; lat <= 80; lat += 20) {
const phi = (90 - lat) * (Math.PI / 180)
const points = []
const radius = Math.sin(phi)
const y = Math.cos(phi)
for (let i = 0; i <= 64; i++) {
const theta = (i / 64) * Math.PI * 2
points.push(new THREE.Vector3(radius * Math.cos(theta), y, radius * Math.sin(theta)))
}
globe.add(new THREE.Line(new THREE.BufferGeometry().setFromPoints(points), gridMaterial))
}
// Add pins at major cities
const locations = [
{ name: 'New York', lat: 40.7128, lon: -74.0060, color: 0xff3333 },
{ name: 'London', lat: 51.5074, lon: -0.1278, color: 0x33ff33 },
{ name: 'Tokyo', lat: 35.6762, lon: 139.6503, color: 0x3333ff },
{ name: 'Sydney', lat: -33.8688, lon: 151.2093, color: 0xffff33 },
{ name: 'Mumbai', lat: 19.0760, lon: 72.8777, color: 0xff33ff }
]
const pins = []
locations.forEach((loc) => {
// Convert lat/lon to 3D coordinates (with +180 offset for texture alignment)
const phi = (90 - loc.lat) * (Math.PI / 180)
const theta = (loc.lon + 180) * (Math.PI / 180)
const x = -Math.sin(phi) * Math.cos(theta)
const z = Math.sin(phi) * Math.sin(theta)
const y = Math.cos(phi)
const pin = new THREE.Group()
pin.userData = {}
pin.position.set(x, y, z)
pin.lookAt(x * 2, y * 2, z * 2)
// Pin stick
const stick = new THREE.Mesh(
new THREE.CylinderGeometry(0.01, 0.01, 0.15, 8),
new THREE.MeshPhongMaterial({ color: loc.color, emissive: loc.color, emissiveIntensity: 0.5 })
)
stick.position.z = 0.075
pin.add(stick)
pin.userData.stick = stick
// Pin head
const head = new THREE.Mesh(
new THREE.SphereGeometry(0.025, 16, 16),
new THREE.MeshPhongMaterial({ color: loc.color, emissive: loc.color, emissiveIntensity: 0.8 })
)
head.position.z = 0.15
pin.add(head)
pin.userData.head = head
// City label
const labelDiv = document.createElement('div')
labelDiv.textContent = loc.name
labelDiv.style.color = '#' + loc.color.toString(16).padStart(6, '0')
labelDiv.style.fontSize = '12px'
labelDiv.style.fontWeight = 'bold'
labelDiv.style.textShadow = '1px 1px 2px rgba(0,0,0,0.8)'
const label = new CSS2DObject(labelDiv)
label.position.z = 0.2
pin.add(label)
pin.userData.label = label
// Add phase for animation
pin.userData.phase = Math.random() * Math.PI * 2
globe.add(pin)
pins.push(pin)
})
// Lighting
scene.add(new THREE.AmbientLight(0xffffff, 0.4))
const light = new THREE.DirectionalLight(0xffffff, 0.8)
light.position.set(5, 3, 5)
scene.add(light)
// Animation loop
const clock = new THREE.Clock()
function animate () {
requestAnimationFrame(animate)
controls.update()
const elapsed = clock.getElapsedTime()
pins.forEach((pin) => {
if (pin.userData.head && pin.userData.phase !== undefined) {
const scale = 1 + Math.sin(elapsed * 2 + pin.userData.phase) * 0.2
pin.userData.head.scale.setScalar(scale)
}
})
renderer.render(scene, camera)
labelRenderer.render(scene, camera)
}
animate()