# How Google Timeline Visualizer Fetches and Stitches Map Tiles: The Complete Pipeline

> Learn how Google Timeline Visualizer fetches and stitches map tiles from OpenStreetMap. Discover the complete pipeline for seamless canvas backgrounds.

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

---

**Google Timeline Visualizer downloads OpenStreetMap tiles from CARTO's CDN by calculating viewport intersections with `requiredTiles()`, fetches up to six tiles concurrently via `loadRequiredTiles()`, and composites them into a seamless canvas background using `drawMapBackground()` with modulo arithmetic to handle the International Date Line.**

The open-source **Google Timeline Visualizer** transforms location history into cinematic journey videos by rendering movement paths atop detailed map backgrounds. At the core of this rendering pipeline lies a sophisticated tile management system that fetches raster tiles from remote servers and composites them into seamless basemaps. Understanding how map tiles are fetched and stitched requires examining the coordinate mathematics, concurrent download strategies, and canvas rendering logic implemented in the TypeScript codebase.

## Step 1: Deriving Required Tiles with `requiredTiles()`

In [`renderer.ts`](https://github.com/mahlernim/google-timeline-visualizer/blob/main/renderer.ts), the `requiredTiles(viewport)` function serves as the entry point for determining which tiles intersect the current camera viewport. This function converts the viewport's geographic bounds into discrete tile coordinates (zoom, x, y) using the formula `2 ** viewport.zoom` to determine the tile count at the given zoom level.

### Handling World Wrap with Modulo Arithmetic

The X coordinate calculation implements robust world-wrapping logic to handle journeys crossing the International Date Line. Rather than allowing coordinates to exceed valid bounds, the code applies modulo arithmetic: `((tileX % tileCount) + tileCount) % tileCount`. This ensures that tiles requesting coordinates beyond the standard range wrap to the opposite side of the map, creating a seamless visual experience when the viewport spans the meridian.

### Clamping Y Coordinate Bounds

For the Y axis, the visualizer clamps values to the valid range `[0, tileCount - 1]`. This prevents requests for non-existent tiles beyond the polar regions, ensuring the tile list contains only valid, fetchable coordinates.

## Step 2: Downloading Tiles with `loadRequiredTiles()`

Once the required coordinates are computed, `loadRequiredTiles(coordinates, ...)` manages the actual HTTP requests. Located in [`renderer.ts`](https://github.com/mahlernim/google-timeline-visualizer/blob/main/renderer.ts), this function constructs URLs using the template `https://a.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png` and manages the concurrent acquisition of PNG resources.

### Concurrent Fetching with Worker Pool

To optimize network throughput without overwhelming the browser, the implementation maintains a worker pool limited to **six parallel fetches**. Each tile download creates an `HTMLImageElement` with `crossOrigin="anonymous"` set, enabling subsequent canvas operations to read pixel data without tainting the canvas. The function returns a `Map<string, HTMLImageElement>` where keys follow the canonical format `${zoom}/${x}/${y}`, allowing O(1) lookup during the rendering phase.

### Error Handling and Cancellation

The downloader respects `AbortSignal` instances passed to `loadRequiredTiles`, enabling cancellation of in-flight requests when the camera moves rapidly. Individual tile errors are silently ignored unless the entire operation is aborted, ensuring that transient network failures don't crash the rendering pipeline.

## Step 3: Rendering and Stitching with `drawMapBackground()`

The final phase occurs in `drawMapBackground(canvas, viewport, tiles)`, which composites the downloaded images into the output raster. This function bridges the gap between geographic coordinates and screen pixels.

### Coordinate Transformations

For each tile in the map, the function first calculates **world coordinates** using `worldX = tileX / tileCount` and `worldY = tileY / tileCount`. These normalized coordinates [0, 1] are then transformed into canvas pixel positions via `worldToCanvas`, accounting for the current viewport's scale and translation.

### Canvas Drawing and Seamless Composition

The tile's dimensions on canvas derive from the viewport's scale factors: `1 / tileCount / (viewport.maxX - viewport.minX)` for width and the analogous calculation for height. The `context.drawImage` API renders each tile at its computed position. Because the X coordinates were previously wrapped during the tile derivation phase, the canvas automatically handles Date Line crossings without additional logic, producing a continuous map background regardless of the journey's global position.

## Complete Implementation Example

To fetch and stitch map tiles manually for custom previews or external tools, you can orchestrate the three pipeline stages using exports from [`renderer.ts`](https://github.com/mahlernim/google-timeline-visualizer/blob/main/renderer.ts) and [`geo.ts`](https://github.com/mahlernim/google-timeline-visualizer/blob/main/geo.ts):

```typescript
import { requiredTiles, loadRequiredTiles, drawMapBackground } from './renderer';
import { viewportFor, unwrapJourneyPoints } from './geo';

// Assume `points` is an array of GeoPoint objects representing the journey
const worldPoints = unwrapJourneyPoints(points);
const viewport = viewportFor(worldPoints, 800); // Prepare 800px width
const tileList = requiredTiles(viewport);
const tileImages = await loadRequiredTiles(tileList);
drawMapBackground(myCanvas, viewport, tileImages);

```

## Key Source Files

The tile management pipeline spans several modules in the repository:

- **[`renderer.ts`](https://github.com/mahlernim/google-timeline-visualizer/blob/main/renderer.ts)**: Contains `requiredTiles`, `loadRequiredTiles`, and `drawMapBackground`—the complete fetch-and-stitch implementation.
- **[`geo.ts`](https://github.com/mahlernim/google-timeline-visualizer/blob/main/geo.ts)**: Provides `unwrapJourneyPoints`, `viewportFor`, and geometric utilities that calculate view bounds from geographic data.
- **[`types.ts`](https://github.com/mahlernim/google-timeline-visualizer/blob/main/types.ts)**: Declares TypeScript interfaces including `Viewport`, `WorldPoint`, and tile coordinate structures.
- **[`camera.ts`](https://github.com/mahlernim/google-timeline-visualizer/blob/main/camera.ts)**: Drives the viewport updates per frame, determining which tiles are required as the virtual camera pans and zooms.

## Summary

- **Google Timeline Visualizer** downloads OpenStreetMap tiles from CARTO's CDN using a zoom/x/y URL template.
- The `requiredTiles()` function determines necessary tiles using `2 ** viewport.zoom` and handles International Date Line crossings via modulo arithmetic on X coordinates.
- `loadRequiredTiles()` fetches up to six tiles concurrently, storing results in a Map keyed by `${zoom}/${x}/${y}`.
- `drawMapBackground()` transforms world coordinates to canvas pixels and uses `drawImage` to stitch tiles, relying on pre-wrapped coordinates to maintain seamlessness across meridians.
- All tile logic resides in [`renderer.ts`](https://github.com/mahlernim/google-timeline-visualizer/blob/main/renderer.ts), with supporting geometry in [`geo.ts`](https://github.com/mahlernim/google-timeline-visualizer/blob/main/geo.ts) and type definitions in [`types.ts`](https://github.com/mahlernim/google-timeline-visualizer/blob/main/types.ts).

## Frequently Asked Questions

### What map tile service does Google Timeline Visualizer use?

The visualizer sources its basemap tiles from **CARTO's Light basemap service**, specifically the URL template `https://a.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png`. These raster tiles are based on OpenStreetMap data and provide a clean, light aesthetic that contrasts well with journey path overlays.

### How does the visualizer handle tiles crossing the International Date Line?

The implementation uses modulo arithmetic in the `requiredTiles()` function to wrap X coordinates: `((tileX % tileCount) + tileCount) % tileCount`. This calculation ensures that tiles requesting coordinates beyond the standard range (e.g., negative indices or values exceeding the tile count) map to the opposite side of the world, allowing seamless rendering of routes that cross the 180th meridian.

### What is the maximum number of concurrent tile downloads?

The `loadRequiredTiles()` function implements a worker pool that limits concurrent downloads to **six parallel fetches**. This constraint balances network utilization against browser resource limits and prevents overwhelming the remote tile server while maintaining responsive map updates during camera movements.

### Where is the tile fetching logic implemented in the source code?

All tile-related operations reside in **[`web/src/renderer.ts`](https://github.com/mahlernim/google-timeline-visualizer/blob/main/web/src/renderer.ts)**, which exports `requiredTiles()` for coordinate calculation, `loadRequiredTiles()` for network acquisition, and `drawMapBackground()` for canvas composition. Supporting geometry calculations live in [`web/src/geo.ts`](https://github.com/mahlernim/google-timeline-visualizer/blob/main/web/src/geo.ts), while TypeScript interfaces are defined in [`web/src/types.ts`](https://github.com/mahlernim/google-timeline-visualizer/blob/main/web/src/types.ts) and viewport updates are managed by [`web/src/camera.ts`](https://github.com/mahlernim/google-timeline-visualizer/blob/main/web/src/camera.ts).