# How LocationOutlierFilter Removes GPS Teleport Spikes in Google Timeline Visualizer

> Discover how the LocationOutlierFilter removes GPS teleport spikes in Google Timeline Visualizer by discarding impossible short duration excursions exceeding 1300 km/h.

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

---

**The LocationOutlierFilter eliminates impossible "teleport" jumps by detecting short-duration excursions exceeding 1300 km/h surrounded by distant points, then discarding all points in those suspicious runs.**

The **LocationOutlierFilter** in **google-timeline-visualizer** is a critical preprocessing component that cleans raw Google Location History exports before visualization. GPS receivers occasionally produce spurious coordinates—often due to satellite handoff errors or signal reflection—that make a device appear to jump thousands of kilometers in minutes. This filter applies a multi-heuristic sliding window algorithm to identify and remove these artifacts while preserving legitimate travel data.

## Core Heuristics Behind the Filter

The filter operates on five simultaneous constraints that together characterize a **teleport spike**:

- **Speed threshold** — Inbound and outbound segments must exceed **1300 km/h**, far beyond any realistic ground transportation
- **Ingress/egress distance** — Entry and exit jumps must each span **≥500 km**
- **Surrounding proximity** — The points before and after the excursion must lie **≤200 km** apart
- **Temporal bounds** — The complete window (before point to after point) must fit within **12 hours**
- **Internal coherence** — Points inside the excursion stay **≤200 km** from each other but **≥500 km** from surrounding points

These constraints are evaluated in [`visualizer.py`](https://github.com/mahlernim/google-timeline-visualizer/blob/main/visualizer.py) by the `_is_suspicious_excursion` function (lines [886-912](https://github.com/mahlernim/google-timeline-visualizer/blob/main/visualizer.py#L886-L912)).

## Step-by-Step Algorithm Implementation

### 1. Speed Calculation Between Points

The `_speed_km_per_hour` helper computes velocity using haversine distance and time deltas. Zero time delta yields infinite speed, immediately flagging duplicate timestamps.

```python

# From visualizer.py, lines 778-783

def _speed_km_per_hour(lat1, lon1, lat2, lon2, timedelta_seconds):
    if timedelta_seconds <= 0:
        return float('inf')
    km = haversine_km(lat1, lon1, lat2, lon2)
    hours = timedelta_seconds / 3600.0
    return km / hours

```

### 2. Excursion Validation

`_is_suspicious_excursion` applies all five heuristics to a candidate window. It receives the full points list plus `start` and `end` indices, then examines the context before `start` and after `end`.

```python

# Pseudocode based on lines 886-912

def _is_suspicious_excursion(points, start, end):
    before = points[start - 1]
    after = points[end + 1]
    first = points[start]
    last = points[end]
    
    # Temporal constraint: full window ≤ 12 hours

    if (after["dt"] - before["dt"]).total_seconds() > 12 * 3600:
        return False
    
    # Surrounding points must be close (≤200 km)

    if haversine_km(before, after) > 200:
        return False
    
    # Ingress and egress must be far jumps (≥500 km)

    if haversine_km(before, first) < 500:
        return False
    if haversine_km(last, after) < 500:
        return False
    
    # Speed checks for both legs

    if _speed_km_per_hour(before, first) <= 1300:
        return False
    if _speed_km_per_hour(last, after) <= 1300:
        return False
    
    # Internal coherence: all internal points ≤200 km from first

    # and ≥500 km from before/after

    for i in range(start, end + 1):
        if haversine_km(points[i], first) > 200:
            return False
        if haversine_km(points[i], before) < 500:
            return False
        if haversine_km(points[i], after) < 500:
            return False
    
    return True

```

### 3. Run Detection and Removal

The main entry point `filter_location_outliers` (lines [325-344](https://github.com/mahlernim/google-timeline-visualizer/blob/main/visualizer.py#L325-L344)) iterates through points, uses `_suspicious_run_end` to find the maximal valid window starting at each position, and skips flagged ranges.

```python
from visualizer import filter_location_outliers
from datetime import datetime

raw_points = [
    {"dt": datetime(2023, 5, 1, 8, 0), "lat": 37.5, "lon": 127.0},      # Seoul

    {"dt": datetime(2023, 5, 1, 8, 10), "lat": 51.5, "lon": -0.1},     # London (impossible!)

    {"dt": datetime(2023, 5, 1, 8, 20), "lat": 37.51, "lon": 127.01},  # Seoul

]

filtered, removed_count = filter_location_outliers(raw_points, mode="conservative")
print(f"Kept {len(filtered)}, removed {removed_count}")  # → Kept 2, removed 1

```

### 4. Optional Filter Disabling

Set `mode="off"` to bypass filtering entirely—useful for debugging or when preserving raw data is required.

```python
filtered_all, removed_all = filter_location_outliers(raw_points, mode="off")
print(f"{len(filtered_all)} points, {removed_all} removed")  # → 3 points, 0 removed

```

## Why These Heuristics Work

| Heuristic | Purpose | Failure Mode Prevented |
|-----------|---------|------------------------|
| **1300 km/h speed limit** | Captures any supersonic jumps | Misses slow drifts (acceptable tradeoff) |
| **≥500 km ingress/egress** | Ensures "teleport" pattern, not local noise | Removes legitimate airport transits* |
| **≤200 km surrounding gap** | Confirms return to original region | Allows detours to nearby cities |
| **≤12 hour window** | Distinguishes spikes from flights | Preserves actual air travel |
| **Internal ≤200 km coherence** | Groups clustered bad points | Handles multi-point GPS glitches |

*The "conservative" mode may filter actual flights under 1300 km/h; users can disable filtering if needed.

## Test Coverage in the Repository

The implementation is validated in [`tests/test_outlier_filter.py`](https://github.com/mahlernim/google-timeline-visualizer/blob/main/tests/test_outlier_filter.py):

- **`test_isolated_gps_spike_is_removed`** — Single point 8000 km away in 10 minutes is discarded
- **`test_multi_point_excursion_cluster_is_removed`** — Consecutive outlier points forming a cluster are both removed
- **`test_filter_mode_off_preserves_all_points`** — `mode="off"` returns input unchanged

## Summary

- **LocationOutlierFilter** uses a **sliding-window multi-heuristic** approach in [`visualizer.py`](https://github.com/mahlernim/google-timeline-visualizer/blob/main/visualizer.py) to detect GPS teleport spikes
- The **1300 km/h speed threshold** combined with **spatial proximity constraints** (≤200 km surrounding, ≥500 km ingress/egress) reliably identifies impossible jumps
- Suspicious runs are **fully discarded** rather than interpolated, preventing contamination of visualization data
- The filter supports **conservative** and **off** modes for flexibility
- All core logic resides in [`visualizer.py`](https://github.com/mahlernim/google-timeline-visualizer/blob/main/visualizer.py) (lines 325-344, 778-783, 886-922) with comprehensive tests in [`tests/test_outlier_filter.py`](https://github.com/mahlernim/google-timeline-visualizer/blob/main/tests/test_outlier_filter.py)

## Frequently Asked Questions

### What GPS error patterns does the LocationOutlierFilter target?

The filter specifically targets **coordinate reflection errors** and **satellite handoff glitches** that produce sudden coordinate jumps followed by rapid return to the true location. These appear as short-duration excursions to distant coordinates with immediate return—physically impossible without supersonic travel.

### Why 1300 km/h as the speed threshold?

**1300 km/h** exceeds all commercial airliners (typically 800-950 km/h cruise) while remaining below actual signal-propagation artifacts. This threshold catches obvious errors without filtering legitimate high-speed rail (320 km/h max) or supersonic flights, which are rare in consumer location history.

### Can the filter remove legitimate travel data?

Yes, in **conservative mode** the filter may flag actual flights if they happen to satisfy the excursion pattern—particularly short-haul flights under 1300 km/h with nearby departure/arrival points. Users should use `mode="off"` when preserving all data is critical, or post-process the `removed` count to audit filtering decisions.

### How does the filter handle multi-point glitch clusters?

The `_suspicious_run_end` function (lines [915-922](https://github.com/mahlernim/google-timeline-visualizer/blob/main/visualizer.py#L915-L922)) extends the candidate window up to three points backward, allowing consecutive bad points that cluster together to be identified and removed as a single unit.