How Great-Circle Interpolation Prevents Map Marker Teleportation in google-timeline-visualizer
Great-circle interpolation prevents map marker teleportation by calculating the shortest spherical path between two GPS coordinates instead of naïvely blending latitude and longitude values, ensuring smooth animation across long-distance journeys like intercontinental flights.
Map marker teleportation occurs when visualization software linearly interpolates between two distant points on a globe. A flight from Tokyo (139°E) to San Francisco (-122°W) would appear to jump instantaneously across the Pacific rather than trace the actual curved path. The mahlernim/google-timeline-visualizer repository solves this using spherical linear interpolation (slerp) in both its TypeScript frontend and Python backend.
Why Linear Latitude/Longitude Interpolation Fails
The root cause of teleportation is straightforward: latitude and longitude are coordinates on a sphere, not Cartesian values.
Consider two points separated by 10,000 kilometers. Simply averaging their latitude and longitude values produces a point that:
- Ignores the Earth's curvature
- Fails to account for longitude wrapping at ±180°
- Produces the shortest path on a flat Mercator projection, not the actual shortest path through 3-D space
The result is a marker that snaps abruptly or takes an unrealistic route across the map.
The Spherical Linear Interpolation (Slerp) Algorithm
The google-timeline-visualizer implements classic slerp in web/src/geo.ts (lines 32-59) and visualizer.py (lines 37-57). The algorithm follows four precise steps:
Step 1: Convert Geographic Points to 3-D Unit Vectors
Each latitude/longitude pair becomes a point on the unit sphere:
const toVector = (point: GeoPoint): [number, number, number] => {
const latitude = (point.latitude * Math.PI) / 180;
const longitude = (point.longitude * Math.PI) / 180;
const latRadius = Math.cos(latitude);
return [
latRadius * Math.cos(longitude),
latRadius * Math.sin(longitude),
Math.sin(latitude),
];
};
This conversion, found at lines 33-41 of geo.ts, places both endpoints in a 3-D Cartesian space where spherical geometry becomes tractable.
Step 2: Compute Angular Distance Between Vectors
The code calculates the central angle between the two unit vectors using the dot product (lines 45-47):
const dot = from[0]*to[0] + from[1]*to[1] + from[2]*to[2];
const angle = Math.acos(Math.min(1, Math.max(-1, dot))); // clamp for floating-point safety
This angle represents the actual arc distance on the sphere's surface.
Step 3: Blend Vectors Using Slerp Weights
For a given fraction of the journey (0.0 to 1.0), the algorithm computes trigonometric weights:
const fromWeight = Math.sin((1 - fraction) * angle) / sinAngle;
const toWeight = Math.sin(fraction * angle) / sinAngle;
const x = from[0] * fromWeight + to[0] * toWeight;
const y = from[1] * fromWeight + to[1] * toWeight;
The sin relationship ensures the interpolated point follows the great-circle arc—the geodesic shortest path—rather than cutting through the sphere's interior (lines 54-57).
Step 4: Project Back to Longitude and Wrap Correctly
The blended vector converts back to geographic coordinates via atan2, then maps to world-X coordinates with proper wrapping (line 58 and surrounding unwrapJourneyPoints logic at lines 90-100).
Fallback Handling for Identical Points
The implementation includes a critical guard against division-by-zero. When two points are virtually identical (sinAngle < 1e-9), the code falls back to simple linear interpolation (lines 48-53):
if (sinAngle < 1e-9) {
// Points are essentially the same—linear interpolation is sufficient
return /* simplified calculation */;
}
This epsilon check ensures numerical stability without affecting visual accuracy for nearby points.
Frontend Implementation: greatCircleReferenceX
The primary TypeScript function greatCircleReferenceX in geo.ts computes world-X coordinates for smooth animation:
import { greatCircleReferenceX } from './geo';
const start = { latitude: 37.7749, longitude: -122.4194 }; // San Francisco
const end = { latitude: 35.6895, longitude: 139.6917 }; // Tokyo
const fraction = 0.25; // 25% of the way
const interpolatedX = greatCircleReferenceX(start, end, fraction);
This returns a world-X coordinate (0–1 scale) that lies precisely on the great-circle path, enabling the rendering engine to position the marker correctly at any animation frame.
Backend Implementation: interpolate_latlon
The Python backend provides equivalent functionality through interpolate_latlon in visualizer.py:
from visualizer import interpolate_latlon
lat1, lon1 = 37.7749, -122.4194 # San Francisco
lat2, lon2 = 35.6895, 139.6917 # Tokyo
frac = 0.25 # 25% along the route
interp_lat, interp_lon = interpolate_latlon(lat1, lon1, lat2, lon2, frac)
print(interp_lat, interp_lon) # 38.79, 176.54 (north Pacific waypoint)
This server-side implementation (lines 37-57) pre-computes path coordinates for clients or generates static visualizations with the same mathematical correctness.
Longitude Wrapping and the unwrapJourneyPoints Function
Great-circle interpolation alone doesn't solve the map discontinuity problem. A flight crossing the antimeridian could still appear to jump between map copies.
The unwrapJourneyPoints function (lines 71-104 in geo.ts) addresses this through the wrapNear utility:
- After computing the great-circle position, it wraps the X coordinate to the nearest world copy
- For polar corridors, it maintains the journey's "core" on a single map copy
- This prevents a Tokyo→San Francisco flight from appearing to reverse direction or loop the long way around
The combination of slerp and coordinate wrapping ensures the camera follows the trip smoothly without sudden jumps.
Performance Characteristics
The slerp implementation trades a modest computational increase for visual correctness:
- Time complexity: O(1) per interpolation point
- Trigonometric overhead: Four
sin/coscalls and oneatan2per calculation - Optimization: The
1e-9epsilon check avoids expensive trigonometry for nearby points
For typical timeline visualizations with hundreds of interpolated frames, this overhead is negligible on modern hardware.
Summary
- Great-circle interpolation replaces naive latitude/longitude blending with spherical linear interpolation (slerp)
- The algorithm converts points to 3-D vectors, computes angular distance, applies trigonometric weights, and projects back to geographic coordinates
- Fallback linear interpolation handles near-identical points to prevent numerical instability
- Coordinate wrapping via
unwrapJourneyPointsensures smooth visual continuity across map boundaries - Both TypeScript (
geo.ts) and Python (visualizer.py) implementations share identical mathematical semantics
Frequently Asked Questions
What causes map marker teleportation in the first place?
Teleportation occurs when visualization code treats latitude and longitude as linear Cartesian values. A direct interpolation between Tokyo (139°E) and San Francisco (-122°W) computes a midpoint near the North Pole or produces a discontinuous jump, rather than following the actual curved flight path across the Pacific. The google-timeline-visualizer prevents this by respecting spherical geometry.
Does great-circle interpolation work for short distances as well?
Yes, but with an optimization. For points separated by less than approximately 1e-9 radians (effectively identical), the code falls back to simple linear interpolation in geo.ts (lines 48-53). This avoids unnecessary trigonometric computation while maintaining sub-pixel accuracy for local movements.
How does this differ from Mercator projection interpolation?
Mercator projection interpolation computes a straight line on the flattened map, which distorts both distance and direction—especially at high latitudes. Great-circle interpolation computes the true shortest path on the sphere's surface, then projects that correct path onto the map. The difference is visually dramatic for trans-polar or long east-west routes.
Can I use the Python interpolate_latlon function for non-visualization purposes?
Absolutely. The function in visualizer.py (lines 37-57) returns raw latitude/longitude pairs suitable for any geospatial application requiring accurate intermediate points: drone flight planning, antenna pointing calculations, or geographic analysis tools. The implementation has no dependencies beyond Python's standard math module.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →