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

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, 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 and 48-50.

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).

// 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) 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) 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).

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). Points whose timestamps fall within any coverage interval are discarded; only uncovered points are retained.

// 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):

  • 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

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 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 which covers edge cases including segments with missing timestamps and path points with invalid geo URI formats.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →