# How Google Timeline Visualizer Detects Long Hops and Transfers

> Learn how the Google Timeline Visualizer detects long hops using timestamp gaps and transfers by comparing device IDs. Understand your location history data better.

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

---

**The visualizer detects long hops by calculating timestamp gaps exceeding 43,200,000 milliseconds (12 hours) and identifies transfers by comparing `deviceId` values between consecutive location points in [`visualizer.py`](https://github.com/mahlernim/google-timeline-visualizer/blob/main/visualizer.py).**

The Google Timeline Visualizer analyzes Google Takeout location history JSON to automatically surface **long hops** (extended temporal gaps) and **transfers** (device switches) on an interactive map. These detection algorithms run during the backend parsing phase before the frontend renders visual indicators.

## Backend Detection Logic in visualizer.py

The core detection mechanism resides in [`visualizer.py`](https://github.com/mahlernim/google-timeline-visualizer/blob/main/visualizer.py), which processes the raw location history array chronologically. The parser maintains a reference to the previous point while iterating through the sorted dataset, applying two distinct heuristics to flag anomalous transitions.

### Long Hop Detection via Timestamp Thresholds

The visualizer defines a hardcoded constant `MAX_GAP_MS` set to `12 * 60 * 60 * 1000` (12 hours in milliseconds). During iteration, the parser calculates the delta between the current point's timestamp and the previous point's timestamp. When this difference exceeds the threshold, the current point is flagged with `is_long_hop = True`.

```python

# visualizer.py

MAX_GAP_MS = 12 * 60 * 60 * 1000  # 12 hours in milliseconds

processed = []
prev = None

for point in sorted_locations:
    if prev:
        delta = point.timestamp_ms - prev.timestamp_ms
        if delta > MAX_GAP_MS:
            point.is_long_hop = True  # Flag for frontend rendering

    processed.append(point)
    prev = point

```

### Device Transfer Detection via deviceId Comparison

Simultaneously, the parser checks for **transfers** by comparing the `deviceId` field between consecutive points. Google Takeout includes this identifier when location data originates from multiple devices (phones, tablets, or wearables). If the identifiers differ, the visualizer marks the transition point with `is_transfer = True`.

```python

# visualizer.py

prev = None

for point in sorted_locations:
    if prev and point.device_id != prev.device_id:
        point.is_transfer = True  # Flag device switch

    # Long hop detection logic runs here

    prev = point

```

## Frontend Visualization in Map.vue

After processing, the backend serves a JSON payload containing the boolean flags `is_long_hop` and `is_transfer`. The frontend component [`web/src/components/Map.vue`](https://github.com/mahlernim/google-timeline-visualizer/blob/main/web/src/components/Map.vue) consumes these flags to differentiate rendering styles.

Long hops display as **dotted polylines** connecting the disconnected segments, while transfers render as **distinct map markers** with specialized icons.

```javascript
// web/src/components/Map.vue
points.forEach((p, i) => {
  if (p.is_long_hop && i > 0) {
    // Render dotted line between previous and current point
    const hopSegment = new google.maps.Polyline({
      path: [prevCoords, currentCoords],
      strokeOpacity: 0,
      icons: [{ icon: { path: 'M 0,-1 0,1', strokeOpacity: 1 }, offset: '0', repeat: '10px' }]
    });
  }
  
  if (p.is_transfer) {
    new google.maps.Marker({
      position: { lat: p.lat, lng: p.lng },
      icon: '/assets/transfer-icon.png',
      map: this.map
    });
  }
});

```

## Summary

- **Long hops** are detected in [`visualizer.py`](https://github.com/mahlernim/google-timeline-visualizer/blob/main/visualizer.py) by comparing consecutive timestamps against the `MAX_GAP_MS` constant (default 12 hours).
- **Transfers** are identified by monitoring changes in the `deviceId` field between consecutive location points during the same parsing iteration.
- Flagged points carry boolean properties (`is_long_hop` and `is_transfer`) that persist through the API response to the frontend.
- The Vue frontend ([`Map.vue`](https://github.com/mahlernim/google-timeline-visualizer/blob/main/Map.vue)) renders long hops as dotted line segments and transfers as specialized map markers for immediate visual recognition.

## Frequently Asked Questions

### What threshold defines a long hop in the visualizer?

The default threshold is **12 hours**, calculated as 43,200,000 milliseconds in the `MAX_GAP_MS` constant within [`visualizer.py`](https://github.com/mahlernim/google-timeline-visualizer/blob/main/visualizer.py). You can modify this value to adjust sensitivity for different travel patterns.

### How does the visualizer distinguish between different devices?

It compares the `deviceId` field between consecutive location points. When this identifier changes—indicating data originated from a different phone, tablet, or wearable—the point is flagged as a transfer.

### Can the detection parameters be customized without modifying source code?

Currently, no. The `MAX_GAP_MS` threshold requires editing [`visualizer.py`](https://github.com/mahlernim/google-timeline-visualizer/blob/main/visualizer.py) directly. The `deviceId` detection relies on Google's export schema and cannot be configured without altering the comparison logic in the source.

### Where are these detections visualized on the interface?

The [`Map.vue`](https://github.com/mahlernim/google-timeline-visualizer/blob/main/Map.vue) component renders **long hops** as dotted polylines connecting distant points and **transfers** as distinct icons at the transition coordinates. A companion [`Timeline.vue`](https://github.com/mahlernim/google-timeline-visualizer/blob/main/Timeline.vue) component lists these events in the textual sidebar.