Zoom Level Constraints for Map Tiles in Google Timeline Visualizer

The Google Timeline Visualizer enforces strict zoom level constraints that limit map tile requests to a minimum of 2 and a maximum of 15, automatically clamping any calculated or user-specified values outside this range.

The mahlernim/google-timeline-visualizer repository implements hard boundaries on map tile zoom levels to balance geographic coverage with tile availability and rendering performance. These constraints appear in both the frontend TypeScript rendering pipeline and the Python backend generation logic, ensuring consistent visualization behavior across interactive web views and static image exports.

Frontend Constraints in camera.ts

The rendering engine defines explicit constants in web/src/camera.ts that govern all tile fetching operations throughout the application.

Defining the Bounds

At lines 76-77, the codebase declares immutable limits that prevent the map from zooming too far out or in:

// web/src/camera.ts
export const MIN_TILE_ZOOM = 2;
export const MAX_TILE_ZOOM = 15;

These constants represent zoom level 2 (worldwide overview) and zoom level 15 (detailed street-level view). The web/src/renderer.ts module imports these values when constructing tile URLs, while web/src/types.ts defines the Viewport interface that references these bounds through its zoom property.

Backend Enforcement in visualizer.py

The Python backend mirrors these constraints when calculating zoom levels for server-side static map image generation.

Calculating and Clamping Zoom

In visualizer.py (lines 96-98), the code computes an initial zoom value using logarithmic scaling based on viewport geometry, then immediately clamps it to the same 2-15 range:


# visualizer.py

zoom = int(math.log2(target_val)) if target_val > 0 else 2
zoom = max(2, min(15, zoom))  # enforce same bounds

This duplication ensures that server-side generation cannot request tiles outside the supported range, maintaining strict parity with the web viewer's limitations regardless of input span or image dimensions.

Practical Implementation Examples

When extending the codebase or implementing custom tile fetching, always apply these constraints before requesting tiles from the provider.

JavaScript Implementation

Use the exported constants from camera.ts to sanitize user input or viewport-driven zoom changes:

import { MIN_TILE_ZOOM, MAX_TILE_ZOOM } from './camera';

function safeZoom(requestedZoom) {
  return Math.max(MIN_TILE_ZOOM, Math.min(MAX_TILE_ZOOM, requestedZoom));
}

// Usage in tile fetching pipeline
const tileZoom = safeZoom(viewport.zoom);
// tileZoom is guaranteed to be between 2 and 15

Python Implementation

Apply identical clamping when preprocessing zoom parameters for static image generation:

import math

def get_map_image(x_center, y_center, span, width_px=800):
    target_val = (2 * MAX_EXTENT * width_px) / (256 * max(span, 1.0))
    zoom = int(math.log2(target_val)) if target_val > 0 else 2
    # Enforce the same 2-15 zoom limits as the frontend

    zoom = max(2, min(15, zoom))
    return fetch_tiles_at_zoom(x_center, y_center, zoom)

Summary

  • Hard limits: The visualizer restricts all map tile zoom levels to a floor of 2 and ceiling of 15 across both rendering contexts.
  • Frontend enforcement: Constants MIN_TILE_ZOOM and MAX_TILE_ZOOM in web/src/camera.ts (lines 76-77) control the WebGL/Canvas rendering pipeline.
  • Backend parity: visualizer.py applies identical clamping logic (lines 96-98) to maintain consistency in static image generation.
  • Implementation pattern: Always use Math.max(2, Math.min(15, value)) in JavaScript or max(2, min(15, zoom)) in Python to ensure compliance before requesting tiles.

Frequently Asked Questions

What happens if I request a zoom level higher than 15?

The system automatically clamps the value to 15. When the safeZoom function or direct boundary checks are applied using MAX_TILE_ZOOM from web/src/camera.ts, any attempt to exceed street-level detail caps at this maximum, preventing 404 errors from tile providers that lack higher-resolution imagery.

Why is the minimum zoom level set to 2 instead of 0?

Zoom level 2 provides sufficient geographic context for timeline visualization while avoiding the extreme distortion and massive tile payload associated with zoom 0 (single world tile) or zoom 1. The MIN_TILE_ZOOM constant at line 76 of web/src/camera.ts enforces this practical lower bound to optimize initial load performance.

Do these constraints affect the static image export feature?

Yes. The Python backend in visualizer.py explicitly applies the same mathematical clamping at lines 96-98, ensuring that generated static maps respect the identical 2-15 range used by the interactive web interface. This guarantees that exported PNG or JPEG images show the same geographic detail as the live browser rendering.

Where else in the codebase are these zoom constraints applied?

Beyond camera.ts, the web/src/renderer.ts module references these bounds when constructing tile URLs for the WebGL viewport, and the Viewport type definition in web/src/types.ts carries the zoom property that remains subject to these limits throughout the rendering pipeline.

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 →