How Google Timeline Visualizer Fetches and Stitches Map Tiles: The Complete Pipeline
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, 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, 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 and geo.ts:
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: ContainsrequiredTiles,loadRequiredTiles, anddrawMapBackground—the complete fetch-and-stitch implementation.geo.ts: ProvidesunwrapJourneyPoints,viewportFor, and geometric utilities that calculate view bounds from geographic data.types.ts: Declares TypeScript interfaces includingViewport,WorldPoint, and tile coordinate structures.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 using2 ** viewport.zoomand 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 usesdrawImageto stitch tiles, relying on pre-wrapped coordinates to maintain seamlessness across meridians.- All tile logic resides in
renderer.ts, with supporting geometry ingeo.tsand type definitions intypes.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, which exports requiredTiles() for coordinate calculation, loadRequiredTiles() for network acquisition, and drawMapBackground() for canvas composition. Supporting geometry calculations live in web/src/geo.ts, while TypeScript interfaces are defined in web/src/types.ts and viewport updates are managed by web/src/camera.ts.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →