# How Is the Transfer Threshold Calculated in Google Timeline Visualizer?

> Understand how the transfer threshold is calculated in Google Timeline Visualizer. Discover its statistical approach for automatic journey segment detection.

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

---

**The transfer threshold in Google Timeline Visualizer uses a robust statistical approach based on median leg length and dispersion, clamped between 60–120 km, to automatically detect journey segments that qualify as transfers.**

The *transfer threshold* determines when a journey segment is long enough to be considered a **transfer** — a point where the visualizer pauses or adjusts camera behavior. This calculation lives in [`web/src/camera.ts`](https://github.com/mahlernim/google-timeline-visualizer/blob/main/web/src/camera.ts) and adapts dynamically to each journey's travel pattern. Understanding how the transfer threshold is calculated helps you debug camera transitions and customize the visualization for different trip types.

## Where the Transfer Threshold Calculation Lives

The core logic resides in **[`web/src/camera.ts`](https://github.com/mahlernim/google-timeline-visualizer/blob/main/web/src/camera.ts)**, specifically within the `transferThreshold` function (lines 19–29). This module also contains `buildLegs`, which consumes the computed threshold to classify journey segments.

- [View `transferThreshold` on GitHub](https://github.com/mahlernim/google-timeline-visualizer/blob/main/web/src/camera.ts#L19-L29)

Supporting type definitions appear in [`web/src/types.ts`](https://github.com/mahlernim/google-timeline-visualizer/blob/main/web/src/types.ts) (`CameraJourney`, `JourneyLeg`), while [`web/src/renderer.ts`](https://github.com/mahlernim/google-timeline-visualizer/blob/main/web/src/renderer.ts) executes the camera animations based on leg classifications. Unit tests in [`tests/test_camera.py`](https://github.com/mahlernim/google-timeline-visualizer/blob/main/tests/test_camera.py) validate the threshold behavior.

## How the Transfer Threshold Is Calculated: Step by Step

The algorithm follows five distinct phases to produce a journey-specific threshold.

### Step 1: Filter Ordinary Leg Distances

The function first extracts consecutive leg distances from `cumulativeDistanceKm`:

```ts
// distances[i] = cumulativeDistanceKm[i] - cumulativeDistanceKm[i-1]

```

Only **positive distances under 120 km** are retained. This filtering excludes:

- Negative values (data errors)
- Zero-length segments
- Extreme outliers (very long jumps that would skew statistics)

### Step 2: Compute the Typical Leg Length

The retained distances are sorted, and the **median** is taken as `typical`:

- The median is robust against irregularities
- It represents the "normal" leg length for this specific journey

### Step 3: Measure Dispersion

For each ordinary distance, the absolute deviation from `typical` is calculated. The median of these deviations becomes `deviation` — a robust estimate of how much leg lengths typically vary.

### Step 4: Calculate Raw Threshold Candidates

Two candidate values are computed:

| Candidate | Formula | Purpose |
|-----------|---------|---------|
| Multiple-based | `typical × 3` | Catches legs significantly longer than normal |
| Deviation-based | `typical + deviation × 6` | Accounts for high-variance journeys |

The **larger** of these two values is selected as the raw threshold.

### Step 5: Clamp to Valid Bounds

The raw threshold is forced into the range **60 km … 120 km** using `Math.max`/`Math.min` or a `clamp` helper. If no ordinary legs exist (all jumps exceed 120 km), the function falls back to **120 km**.

## Applying the Transfer Threshold in Practice

The computed threshold drives segment classification in `buildLegs`:

```ts
const threshold = transferThreshold(journey.cumulativeDistanceKm);
if (endKm - startKm < Math.max(1, threshold)) continue;   // skip tiny legs

```

Segments shorter than the threshold are treated as continuous motion; longer segments trigger transfer behavior (camera pause, view reset, etc.).

### Complete Usage Example

```ts
import { buildLegs, transferThreshold } from './camera';

// Example journey with cumulative distances in km
const journey = {
  worldPoints: [...],  // coordinates
  cumulativeDistanceKm: [0, 5, 12, 18, 25, 45, 120, 125]
};

// Compute threshold for this specific journey
const thresh = transferThreshold(journey.cumulativeDistanceKm);
console.log(`Transfer threshold: ${thresh} km`);
// Output: threshold between 60–120 km based on median leg patterns

// Build classified legs
const legs = buildLegs(journey);

/* legs contains:
   {
     startKm: number,
     endKm: number,
     isTransfer: boolean  // true if segment exceeds threshold
   }
*/

```

## Why This Statistical Approach Works

The transfer threshold calculation uses **median-based robust statistics** rather than mean-based methods for good reason:

- **Median** resists influence from occasional very long or very short legs
- **Median absolute deviation (MAD)** provides stable spread estimation
- The **max-of-two-candidates** strategy handles both consistent and variable journey patterns
- **Hard bounds (60–120 km)** prevent pathological thresholds on unusual journeys

This design ensures the visualizer behaves reasonably across diverse travel types — from dense urban commutes with 2 km legs to cross-country road trips with 80 km stretches.

## Key Files and Functions

| File | Key Elements |
|------|--------------|
| [`web/src/camera.ts`](https://github.com/mahlernim/google-timeline-visualizer/blob/main/web/src/camera.ts) | `transferThreshold()` (lines 19–29), `buildLegs()` |
| [`web/src/types.ts`](https://github.com/mahlernim/google-timeline-visualizer/blob/main/web/src/types.ts) | `CameraJourney`, `JourneyLeg` interfaces |
| [`web/src/renderer.ts`](https://github.com/mahlernim/google-timeline-visualizer/blob/main/web/src/renderer.ts) | Consumes leg data for camera animation |
| [`tests/test_camera.py`](https://github.com/mahlernim/google-timeline-visualizer/blob/main/tests/test_camera.py) | Unit tests for threshold and leg logic |

## Summary

- The **transfer threshold** is calculated in [`web/src/camera.ts`](https://github.com/mahlernim/google-timeline-visualizer/blob/main/web/src/camera.ts) using median-based robust statistics
- Only ordinary legs under 120 km inform the calculation, filtering outliers
- Two candidates (`typical × 3` vs. `typical + deviation × 6`) compete; the larger wins
- Final threshold is **clamped to 60–120 km**, with 120 km as fallback
- `buildLegs` uses this threshold to mark segments as transfers via `isTransfer`

## Frequently Asked Questions

### What happens if a journey has no legs under 120 km?

The `transferThreshold` function detects this edge case and returns **120 km** as the fallback value. All segments then qualify for potential transfer detection, preventing division by zero or empty-array errors in the median calculations.

### Why use median instead of mean for the typical leg length?

The **median** is robust against outliers. A single 500 km flight segment among city hops would wildly skew a mean-based threshold, whereas the median remains representative of the typical travel pattern. This matches the visualizer's goal of adapting to *common* journey characteristics.

### Can I override the 60–120 km clamp bounds?

The bounds are hardcoded in [`camera.ts`](https://github.com/mahlernim/google-timeline-visualizer/blob/main/camera.ts). To customize them, you would modify the `clamp` call within `transferThreshold` or post-process the returned value in your own wrapper. The repository does not expose these as configuration parameters.

### How does `buildLegs` use the threshold differently for walking vs. driving journeys?

It doesn't — the threshold adapts automatically. A walking journey with 1 km median legs yields a threshold near 60 km (the floor), while a driving journey with 30 km median legs likely hits `typical × 3 = 90 km`. The statistical method self-tunes to each journey's scale without mode-specific logic.