Google Timeline Coordinate Parser: What Formats the `parse_coordinate` Function Handles

The parse_coordinate function in visualizer.py normalizes five distinct coordinate formats from Google Timeline exports: Android Takeout URIs, iOS Takeout strings, JSON objects with latLng or point keys, scaled micro-degree integers, and raw decimal strings—returning standard (lat, lon) tuples or None for invalid data.

Google Timeline exports vary significantly between Android and iOS devices, with additional variation between Takeout archives and direct device exports. The parse_coordinate utility function (lines 85–104 in visualizer.py) unifies these disparate formats into consistent floating-point coordinates. This article examines each supported format using the actual source code from the mahlernim/google-timeline-visualizer repository.

Android Takeout URI Format

Android Takeout exports frequently encode coordinates as geo-scheme URIs containing optional query parameters and formatting artifacts.

The function handles strings like:

parse_coordinate('geo:37.4219983,-122.084?z=15')

# → (37.4219983, -122.084)

The processing sequence stripps three components:

  • geo: prefix — removed via removeprefix('geo:')
  • Query string — everything after ? discarded using split('?', 1)[0]
  • Degree symbols and whitespace — deleted with replace('°', '').replace(' ', '')

iOS Takeout String Format

iOS exports omit the geo: scheme but may include degree symbols (°) and extra spacing:

parse_coordinate('37.4219983° , -122.084°')

# → (37.4219983, -122.084)

The same cleaning pipeline applies: degree symbols and whitespace removal, followed by comma-splitting to extract latitude and longitude values.

JSON Object Wrappers

Some Timeline exports embed coordinates in dictionary structures with either latLng or point keys:

parse_coordinate({'latLng': '37.4219983,-122.084'})

# → (37.4219983, -122.084)

parse_coordinate({'point': '37.4219983,-122.084'})

# → (37.4219983, -122.084)

As implemented in visualizer.py, the function checks isinstance(value, dict) and extracts the inner string via:

value = value.get('latLng') or value.get('point')

This dual-key support accommodates variations between Google Timeline export versions.

Scaled Integer Coordinates (Micro-Degrees)

Certain Android exports represent coordinates as integers scaled by 10,000,000 (micro-degrees) rather than floating-point decimals. The parse_coordinate function auto-detects this format:

parse_coordinate('374219983,-1220840000')

# → (37.4219983, -122.084)

The detection logic (lines 97–99 in visualizer.py) triggers when absolute values exceed 1,000,000:

if abs(lat) > 1_000_000 or abs(lon) > 1_000_000:
    lat /= 10_000_000
    lon /= 10_000_000

This automatic rescaling eliminates manual preprocessing for high-precision Android Timeline data.

Invalid Input Handling

The parse_coordinate function returns None for any input failing validation:

  • Empty strings or non-string types
  • Missing comma separators
  • Non-numeric components
  • Out-of-range coordinates: latitude outside [-85.05112878, 85.05112878] (Web-Mercator limits) or longitude outside [-180, 180]
parse_coordinate('invalid-coordinate')  # → None

parse_coordinate('')                     # → None

parse_coordinate({'otherKey': 'value'})  # → None (no latLng or point)

Complete Implementation Reference

Here is the full parse_coordinate function from visualizer.py (lines 85–104) showing how Google Timeline coordinate formats are unified:

def parse_coordinate(value: Any) -> Optional[Tuple[float, float]]:
    """Parse coordinate values used by Android, iOS, and Takeout exports."""
    if isinstance(value, dict):
        value = value.get('latLng') or value.get('point')
    if not isinstance(value, str) or not value.strip():
        return None
    cleaned = value.strip().removeprefix('geo:').split('?', 1)[0]\
                .replace('°', '').replace(' ', '')
    parts = cleaned.split(',')
    if len(parts) < 2:
        return None
    try:
        lat, lon = float(parts[0]), float(parts[1])
    except ValueError:
        return None
    if abs(lat) > 1_000_000 or abs(lon) > 1_000_000:
        lat /= 10_000_000
        lon /= 10_000_000
    if not (-85.05112878 <= lat <= 85.05112878 and -180 <= lon <= 180):
        return None
    return lat, lon

Supported Format Summary Table

Format Example Input Output
Android geo-URI 'geo:37.4219983,-122.084?z=15' (37.4219983, -122.084)
iOS decorated string '37.4219983° , -122.084°' (37.4219983, -122.084)
latLng object {'latLng': '37.4219983,-122.084'} (37.4219983, -122.084)
point object {'point': '37.4219983,-122.084'} (37.4219983, -122.084)
Micro-degree integers '374219983,-1220840000' (37.4219983, -122.084)
Invalid/missing data 'invalid' or {} None

Testing and Validation

The project's test suite in tests/test_parser.py verifies correct behavior across all supported Google Timeline coordinate formats. Running these tests ensures compatibility with new export variations as Google's formats evolve.

Summary

  • Five input types: Android Takeout URIs, iOS strings, latLng/point JSON objects, micro-degree integers, and raw decimal strings
  • Automatic normalization: prefix stripping, query removal, degree symbol cleaning, and scaled-integer detection
  • Robust validation: Web-Mercator latitude bounds and global longitude limits with None return for invalid data
  • Single function interface: parse_coordinate in visualizer.py handles all Google Timeline export variations without caller-side preprocessing

Frequently Asked Questions

What Google Timeline exports use the geo: URI prefix?

Android Takeout archives typically wrap coordinates in geo:latitude,longitude format, often followed by query parameters like ?z=15 for zoom level. The parse_coordinate function automatically strips these components to extract raw coordinate values.

How does the parser handle iOS export differences?

iOS Timeline exports omit the geo: scheme but frequently include degree symbols (°) and irregular spacing. The function applies identical cleaning operations—removing ° and whitespace—so both Android and iOS strings normalize to the same output format.

What happens when coordinates are stored as integers?

Some Android exports use micro-degree encoding (values multiplied by 10,000,000). When parse_coordinate detects values exceeding 1,000,000 in magnitude, it automatically divides by 10,000,000 to convert to standard decimal degrees without requiring manual intervention.

Why does the function enforce Web-Mercator latitude limits?

The latitude bound of ±85.05112878° matches the mathematical limits of the Web-Mercator projection used by most mapping libraries. Coordinates outside this range or with longitude beyond ±180° return None, preventing downstream visualization errors in tools like Folium or Leaflet.

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 →