# How TimelineParser Reconciles Path Points with Semantic Intervals in Google Timeline Data

> Discover how TimelineParser reconciles path points with semantic intervals by merging temporal spans and filtering raw data for accurate Google Timeline visualization. Learn more!

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

---

**The `TimelineParser` reconciles path points with semantic intervals by recording temporal spans from semantic segments, merging overlapping intervals into a coverage map, and filtering out raw path points that fall within those covered time ranges.**

Google Timeline exports contain two fundamentally different types of location data: **semantic points** (activities and place visits) and **raw path points** (fine-grained GPS traces). The `mahlernim/google-timeline-visualizer` project solves the challenge of merging these data sources without redundancy. In [`web/src/timeline.ts`](https://github.com/mahlernim/google-timeline-visualizer/blob/main/web/src/timeline.ts), the parser implements a four-stage reconciliation pipeline that prioritizes semantic accuracy while preserving path detail only where it adds new information.

## Collecting Points from Timeline Segments

For every segment in the raw export, the parser extracts both data types simultaneously.

**Semantic points** are generated from three sources per segment:
- `activity.start` — the departure location
- `activity.end` — the arrival location  
- `visit.topCandidate.placeLocation` — the confirmed place when available

These are added via `addPoint` as shown at lines [19-25](https://github.com/mahlernim/google-timeline-visualizer/blob/main/web/src/timeline.ts#L19-L25) and [48-50](https://github.com/mahlernim/google-timeline-visualizer/blob/main/web/src/timeline.ts#L48-L50).

**Path points** come from `segment.timelinePath`, an array of fine-grained GPS readings. The parser iterates these and converts each to a `GeoPoint` object (lines [27-44](https://github.com/mahlernim/google-timeline-visualizer/blob/main/web/src/timeline.ts#L27-L44)).

```ts
// Simplified excerpt from web/src/timeline.ts
interface TimelineSegment {
  startTime: string;
  endTime: string;
  activity?: { start: string; end: string };
  visit?: { topCandidate: { placeLocation: string } };
  timelinePath?: Array<{ time: string; point: string }>;
}

function parseSegment(segment: TimelineSegment): ParsedSegment {
  const points: GeoPoint[] = [];
  
  // Semantic extraction
  if (segment.activity) {
    addPoint(points, segment.activity.start, segment.startTime);
    addPoint(points, segment.activity.end, segment.endTime);
  }
  
  // Path extraction
  const pathPoints = segment.timelinePath?.map(p => 
    new GeoPoint(p.point, p.time)
  ) ?? [];
  
  return { points, pathPoints, hasSemantic: points.length > 0 };
}

```

## Building and Merging Semantic Intervals

When a segment contains at least one semantic point, the parser records its temporal span as a **semantic interval**. The `semanticInterval` function (lines [27-34](https://github.com/mahlernim/google-timeline-visualizer/blob/main/web/src/timeline.ts#L27-L34)) creates an interval from `startTime` to `endTime`. All intervals accumulate in the `semanticIntervals` array.

Raw timeline data frequently contains overlapping segments — for example, a place visit nested within a broader activity. The `mergeIntervals` function (lines [36-48](https://github.com/mahlernim/google-timeline-visualizer/blob/main/web/src/timeline.ts#L36-48)) handles this by:

1. Sorting intervals by start time
2. Collapsing any intersecting intervals into a single continuous span
3. Producing a minimal set of non-overlapping **coverage intervals**

This merged coverage map represents all time periods where semantic data already provides location information.

## Filtering Path Points Against Coverage

The critical reconciliation step occurs after all segments are parsed. The parser normalizes segment direction via `normalizeSegmentDirection`, then processes segments in a `flatMap` operation (lines [66-70](https://github.com/mahlernim/google-timeline-visualizer/blob/main/web/src/timeline.ts#L66-L70)).

For segments flagged as `standalonePath` (containing only path points, no semantic data), each point undergoes an `isCovered` check against the merged semantic intervals (lines [50-61](https://github.com/mahlernim/google-timeline-visualizer/blob/main/web/src/timeline.ts#L50-61)). Points whose timestamps fall within any coverage interval are **discarded**; only uncovered points are retained.

```ts
// Conceptual flow of the filtering logic
const coverageIntervals = mergeIntervals(semanticIntervals);

const reconciledPoints = segments.flatMap(segment => {
  if (segment.standalonePath) {
    // Keep path points only where semantic data is absent
    return segment.pathPoints.filter(
      point => !isCovered(point.instant, coverageIntervals)
    );
  }
  // Segments with semantic points use those directly
  return segment.semanticPoints;
});

```

## Final Deduplication and Ordering

The reconciled point list undergoes two final transformations (lines [71-80](https://github.com/mahlernim/google-timeline-visualizer/blob/main/web/src/timeline.ts#L71-80)):

- **Deduplication** by composite key: `instant + latitude + longitude`
- **Chronological sorting** — provided all points have valid timezone data

This produces a single, clean `GeoPoint[]` that represents the user's travel history without temporal gaps or redundant location records.

## Complete Usage Example

```ts
import { parseTimelineJson } from './web/src/timeline';
import type { TimelineExport } from './web/src/types';

const exportData: TimelineExport = {
  semanticSegments: [
    {
      // Morning commute with both semantic and path data
      startTime: '2024-01-15T08:00:00-08:00',
      endTime: '2024-01-15T08:45:00-08:00',
      activity: {
        start: 'geo:37.7749,-122.4194',  // Home
        end: 'geo:37.8024,-122.4058'      // Office
      },
      timelinePath: [
        { time: '2024-01-15T08:05:00-08:00', point: 'geo:37.7760,-122.4190' },
        { time: '2024-01-15T08:15:00-08:00', point: 'geo:37.7840,-122.4120' },
        { time: '2024-01-15T08:30:00-08:00', point: 'geo:37.7950,-122.4080' }
      ]
    },
    {
      // GPS-only segment (no semantic data)
      startTime: '2024-01-15T12:00:00-08:00',
      endTime: '2024-01-15T12:30:00-08:00',
      timelinePath: [
        { time: '2024-01-15T12:05:00-08:00', point: 'geo:37.8030,-122.4070' },
        { time: '2024-01-15T12:20:00-08:00', point: 'geo:37.8050,-122.4100' }
      ]
    }
  ]
};

const geoPoints = parseTimelineJson(exportData);

// Result: 4 points total
// - 2 semantic points from the commute (start and end)
// - 0 path points from the commute (covered by 08:00-08:45 interval)
// - 2 path points from the lunch walk (standalone, no coverage)

```

## Summary

- **Dual extraction**: The parser captures both semantic points (activities/visits) and raw path points from every segment
- **Interval tracking**: Semantic segments generate temporal intervals that map when high-quality location data exists
- **Coverage merging**: Overlapping intervals collapse into a minimal set, preventing edge cases from fragmenting the filter
- **Selective retention**: Path points survive only when their timestamps fall outside all merged semantic intervals
- **Final polish**: Deduplication and chronological sorting produce analysis-ready output

This approach ensures that downstream visualizations prioritizes human-readable place visits and activity endpoints while preserving GPS trace detail exclusively for time periods lacking semantic enrichment.

## Frequently Asked Questions

### Why filter path points instead of using all available data?

Path points are dense, noisy, and spatially imprecise compared to semantic points. Google derives semantic locations through place matching and user confirmation, making them more accurate for visited locations. The parser's `isCovered` logic (see [`web/src/timeline.ts`](https://github.com/mahlernim/google-timeline-visualizer/blob/main/web/src/timeline.ts) lines 50-61) eliminates redundant low-quality data while preserving GPS traces only for gaps in semantic coverage — typically transit between places or unlabeled movement.

### How does the parser handle overlapping semantic intervals?

The `mergeIntervals` function sorts all semantic intervals by start time, then iteratively merges any that intersect. This addresses real-world cases like a restaurant visit nested within a broader "traveling" activity segment. The resulting non-overlapping coverage map ensures the `isCovered` check behaves deterministically regardless of how segments overlap in the source export (lines 36-48).

### What qualifies a segment as `standalonePath`?

A segment receives the `standalonePath` flag when parsing yields zero semantic points but one or more path points. This occurs when Google Timeline recorded movement without identifying it as an activity or place visit. These segments are the only candidates for path point retention, and even then each point must survive the coverage filter (lines 66-70).

### Can this reconciliation logic handle malformed or partial Timeline exports?

Yes — the parser defensively handles missing fields. `activity` and `visit` objects are optional; `timelinePath` defaults to empty array via nullish coalescing. The `normalizeSegmentDirection` step also corrects temporal ordering issues that appear in some exports. For validation, see [`web/src/timeline.test.ts`](https://github.com/mahlernim/google-timeline-visualizer/blob/main/web/src/timeline.test.ts) which covers edge cases including segments with missing timestamps and path points with invalid geo URI formats.