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/cos calls and one atan2 per calculation
  • Optimization: The 1e-9 epsilon 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 unwrapJourneyPoints ensures 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →