# How the Dynamic Camera Movement Adjusts Zoom Per Segment in Google Timeline Visualizer

> Discover how dynamic camera movement in Google Timeline Visualizer adjusts zoom per segment using context-aware sampling and exponential smoothing for seamless transitions. Learn more!

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

---

**The dynamic camera movement calculates per-segment zoom using context-aware sampling with leg-specific parameters and applies directional exponential smoothing to create smooth transitions between different travel legs.**

The Google Timeline Visualizer renders location history as animated map videos, with the dynamic camera mode offering intelligent zoom adjustments that adapt to varying travel distances and transfer legs. Unlike fixed or steady modes, this algorithm implemented in [`visualizer.py`](https://github.com/mahlernim/google-timeline-visualizer/blob/main/visualizer.py) computes optimal zoom levels for each segment of the journey while maintaining fluid transitions between different scales.

## Camera Configuration Parameters

The dynamic behavior is defined in the `CAMERA_MOVEMENTS` dictionary at lines 72-75 of [`visualizer.py`](https://github.com/mahlernim/google-timeline-visualizer/blob/main/visualizer.py). The configuration uses specific parameters to control context sizing and smoothing behavior:

```python
'dynamic': dict(
    context_fraction=0.10,          # 10% of total route length is used as "look-ahead" context

    minimum_context_km=100.0,       # never shrink context below 100 km

    maximum_context_km=350.0,       # never exceed 350 km

    padding=2.2,                    # extra space around the focus points

    minimum_span=0.00045,           # smallest allowed map span (in Mercator units)

    zoom_out_alpha=0.24,            # smoothing factor when zooming out

    zoom_in_alpha=0.06,             # smoothing factor when zooming in

    leg_aware=True,                 # treat "transfer" legs specially

    fixed_zoom=False)               # allow per-frame span changes

```

The **context fraction** determines a base context distance proportional to the whole trip, clamped between `minimum_context_km` and `maximum_context_km`. When `leg_aware` is enabled, the algorithm replaces this generic context with the exact length of transfer legs, producing tighter zoom levels when moving quickly between distant points. The **padding** parameter expands the computed focus rectangle to keep the path comfortably inside the frame, while `minimum_span` establishes a floor for the smallest allowed map span.

## Per-Segment Zoom Calculation with `raw_camera_sample`

The `raw_camera_sample` function (lines 41-78 in [`visualizer.py`](https://github.com/mahlernim/google-timeline-visualizer/blob/main/visualizer.py)) computes the zoom span for a single distance point. This function implements the per-segment logic that adapts camera framing to current travel conditions.

### Leg-Aware Context Adaptation

When processing each sample, the function first retrieves the movement configuration, then determines the appropriate context distance. If `leg_aware=True` and the current segment represents a transfer leg, the algorithm uses the leg's own length rather than the generic context fraction:

1. **Retrieve movement config**: `movement = CAMERA_MOVEMENTS[movement_name]`
2. **Determine context**: For transfer legs, use `leg[1] - leg[0]`; otherwise use the clamped context fraction
3. **Select range**: Define `tail_distance` and `lookahead_distance` windows around the current position
4. **Collect focus points**: Gather all map points within the window, ensuring the current location remains centered

### Focus Point and Span Computation

After establishing the focus set including exact start, current, and end points, the function calculates the raw span. The computation applies the padding multiplier and enforces the minimum span constraint:

```python
span = max(extent * movement['padding'], movement['minimum_span'])

```

This result represents the **target zoom** for that specific sample, which subsequent smoothing operations will blend into the final camera trajectory.

## Smoothing the Camera Track via Exponential Interpolation

The `build_camera_track` function (lines 80-90) constructs the complete camera trajectory by invoking `raw_camera_sample` for every sample point—defaulting to 480 samples across the journey. Since `fixed_zoom` remains `False` for dynamic mode, the algorithm maintains per-frame span changes rather than forcing a global zoom level.

### Direction-Aware Smoothing Factors

The critical smoothing logic occurs at lines 100-104, where the algorithm applies different interpolation weights depending on zoom direction:

- **Zooming out** uses `zoom_out_alpha=0.24`, creating slower, more gradual pullbacks
- **Zooming in** uses `zoom_in_alpha=0.06`, enabling quicker adaptation when entering detailed areas

This directional awareness prevents jarring zoom jumps while allowing rapid response when focusing on dense urban segments after long transfers.

### The Zoom Interpolation Formula

The smoothing implementation uses exponential interpolation to compute the geometric mean between previous and target spans:

```python
alpha = movement['zoom_out_alpha'] if target_span > previous_span else movement['zoom_in_alpha']
span = target_span if movement['fixed_zoom'] else math.exp(
    math.log(previous_span) + (math.log(target_span) - math.log(previous_span)) * alpha,
)

```

The exponential approach ensures natural-looking zoom curves that react faster to zoom-in events—such as entering a dense city—while easing out when pulling back from lengthy transfers between distant locations.

## Implementing Dynamic Camera Movement

You can invoke the dynamic camera mode programmatically using the core functions from [`visualizer.py`](https://github.com/mahlernim/google-timeline-visualizer/blob/main/visualizer.py):

```python
from visualizer import (
    parse_timeline,
    build_journey_timing,
    build_camera_track,
    camera_at,
)

# Load and parse timeline data

timestamps, xs, ys, cum_dist, lats, lons = parse_timeline("Timeline.json", 2023)

# Create distance-to-time mapper

distance_at = build_journey_timing(cum_dist, "balanced")

# Build the dynamic camera track with leg-aware zoom adjustments

camera_track = build_camera_track(
    cum_dist, xs, ys, lats, lons,
    movement_name="dynamic",
    distance_at=distance_at,
)

# Query camera pose at 42% progress

center_x, center_y, span = camera_at(camera_track, progress=0.42)
print(f"Center: ({center_x:.0f}, {center_y:.0f})  Span: {span:.0f} Mercator units")

```

For command-line usage, specify the dynamic movement via the `--camera-movement` flag:

```bash
python visualizer.py --input Timeline.json --year 2023 \
    --camera-movement dynamic --output dynamic_demo.mp4

```

## Summary

- **Configuration-driven behavior**: The `CAMERA_MOVEMENTS['dynamic']` dictionary in [`visualizer.py`](https://github.com/mahlernim/google-timeline-visualizer/blob/main/visualizer.py) defines context fractions, padding, and smoothing parameters that control per-segment zoom calculations.
- **Leg-aware adaptation**: When `leg_aware=True`, the algorithm detects transfer legs and adjusts context distance to the exact leg length, creating tighter zoom framing during rapid long-distance travel.
- **Dual-phase smoothing**: The system applies `zoom_out_alpha=0.24` for gradual pullbacks and `zoom_in_alpha=0.06` for rapid focusing, ensuring natural transitions between different map scales.
- **Exponential interpolation**: Smooth camera tracks are generated using geometric mean calculations between previous and target spans, preventing abrupt zoom changes while maintaining responsiveness.

## Frequently Asked Questions

### What is the difference between zoom_out_alpha and zoom_in_alpha?

The `zoom_out_alpha` parameter (0.24) controls how quickly the camera pulls back when transitioning to wider views, using a higher smoothing factor for gradual, cinematic zoom-outs. Conversely, `zoom_in_alpha` (0.06) uses a lower value to enable faster, more responsive zooming when focusing on detailed areas, ensuring the camera adapts quickly to dense urban environments without lag.

### How does the leg_aware parameter affect the camera zoom?

When `leg_aware=True`, the camera system inspects the current travel leg type during `raw_camera_sample` execution. For segments flagged as transfers, the algorithm replaces the generic context fraction with the exact leg distance, resulting in tighter zoom levels that focus specifically on the transfer route rather than maintaining a broader context view.

### Why does the dynamic mode use exponential interpolation instead of linear?

The exponential interpolation implemented at lines 100-104 calculates the geometric mean between previous and target spans using logarithmic space. This approach produces naturally accelerating and decelerating zoom curves that match human visual perception better than linear interpolation, creating cinematic transitions that feel physically correct when moving between different map scales.

### Can I adjust the minimum and maximum context distances?

Yes, you can modify the `minimum_context_km` and `maximum_context_km` values in the `CAMERA_MOVEMENTS['dynamic']` configuration dictionary. These parameters clamp the context distance calculated from `context_fraction`, ensuring the camera never zooms too tightly (below 100 km) or too widely (above 350 km) regardless of the total trip length or specific leg characteristics.