# How Great-Circle Interpolation Prevents Map Marker Teleportation in google-timeline-visualizer

> Great-circle interpolation stops map marker teleportation by finding the shortest path between GPS points, not just blending coordinates. See smooth animations for global travel.

- Repository: [mahlernim/google-timeline-visualizer](https://github.com/mahlernim/google-timeline-visualizer)
- Tags: deep-dive
- Published: 2026-08-23

---

**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`](https://github.com/mahlernim/google-timeline-visualizer/blob/main/web/src/geo.ts) (lines 32-59) and [`visualizer.py`](https://github.com/mahlernim/google-timeline-visualizer/blob/main/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:

```ts
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`](https://github.com/mahlernim/google-timeline-visualizer/blob/main/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):

```ts
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:

```ts
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):

```ts
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`](https://github.com/mahlernim/google-timeline-visualizer/blob/main/geo.ts) computes world-X coordinates for smooth animation:

```ts
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`](https://github.com/mahlernim/google-timeline-visualizer/blob/main/visualizer.py):

```python
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`](https://github.com/mahlernim/google-timeline-visualizer/blob/main/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`](https://github.com/mahlernim/google-timeline-visualizer/blob/main/geo.ts)) and Python ([`visualizer.py`](https://github.com/mahlernim/google-timeline-visualizer/blob/main/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`](https://github.com/mahlernim/google-timeline-visualizer/blob/main/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`](https://github.com/mahlernim/google-timeline-visualizer/blob/main/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.