Interactive 3D Globe with Geographic Data Pins
Stunning Three.js 3D globe visualization with animated location pins marking major world cities. Features realistic globe rendering, latitude/longitude grid lines, auto-rotation, and customizable pins for geographic data visualization and interactive mapping.
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.
JavaScript (plain)
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()