Web Mercator Projection Formula in the Google Timeline Visualizer: Implementation Guide
The Google Timeline Visualizer converts geographic coordinates (latitude φ and longitude λ) into Web Mercator (Pseudo-Mercator) coordinates using the formulas x = R × λ and y = R × ln(tan(π/4 + φ/2)), where R equals 6,378,137 meters.
The Web Mercator projection formula enables seamless alignment between real-world GPS data and standard map tile systems. In the mahlernim/google-timeline-visualizer repository, this critical conversion is implemented as a Kotlin singleton that bridges geographic coordinates with the SVG canvas used for rendering timeline visualizations.
Mathematical Foundation of the Web Mercator Projection
The implementation follows the standard Web Mercator (EPSG:3857) mathematical model. The projection transforms spherical coordinates into a cylindrical map projection suitable for web mapping applications.
The core formulas implemented in the codebase are:
| Component | Formula | Description |
|---|---|---|
| X-coordinate | x = R × Math.toRadians(lon) |
East-west position in meters |
| Y-coordinate | y = R × Math.log(Math.tan(Math.PI / 4 + Math.toRadians(lat) / 2)) |
North-south position in meters |
Where R = 6378137.0 represents the Earth's authalic radius in meters, which is the standard radius used by the Web Mercator specification.
Kotlin Implementation in TimelineModels.kt
The projection logic resides in the WebMercator object within app/src/main/java/dev/mahlernim/timelinevisualizer/model/TimelineModels.kt (lines 445-462). This singleton provides two overloaded project methods for coordinate conversion.
object WebMercator {
private const val RADIUS = 6378137.0 // metres
/** Convert a latitude / longitude pair to Web‑Mercator X/Y. */
fun project(lat: Double, lon: Double): Point {
val x = RADIUS * Math.toRadians(lon)
val y = RADIUS *
Math.log(
Math.tan(Math.PI / 4 + Math.toRadians(lat) / 2)
)
return Point(x, y)
}
/** Overload that accepts a GeoPoint and returns a ProjectedPoint. */
fun project(p: GeoPoint): ProjectedPoint = project(p.latitude, p.longitude)
}
The project function applies the standard Web Mercator projection formula by first converting degrees to radians, then computing the logarithmic transformation for the Y-coordinate. This implementation ensures precise alignment with map tiles served by standard web mapping services.
Integration with the Rendering Pipeline
The visualizer invokes WebMercator.project throughout the rendering pipeline to position timeline elements accurately on the map canvas. Specifically, in app/src/main/java/dev/mahlernim/timelinevisualizer/render/TimelinePainter.kt (lines 169-171), the projection converts geographic points into SVG-compatible coordinates.
// Example from TimelinePainter.kt
val currentPoint = timelinePainter.currentPoint // GeoPoint
val mercatorPos = WebMercator.project(currentPoint) // Point used for SVG positioning
svgElement.setAttribute("transform",
"translate(${mercatorPos.x}, ${mercatorPos.y})")
This integration ensures that GPS coordinates from Google Timeline data render at the correct pixel positions relative to the Web Mercator base map.
Practical Code Examples
Projecting Individual Coordinates
Convert specific latitude and longitude values to planar coordinates:
// Example 1: Project a single coordinate
val projected = WebMercator.project(37.7749, -122.4194) // San Francisco
println("X = ${projected.x}, Y = ${projected.y}")
Processing GeoPoint Objects
Transform timeline data points using the overloaded method:
// Example 2: Project a GeoPoint from the timeline model
val geo = GeoPoint(
instant = Instant.parse("2023-01-01T00:00:00Z"),
latitude = 48.8566,
longitude = 2.3522
)
val projected = WebMercator.project(geo)
println(projected) // prints a Point with Mercator X/Y in metres
Canvas Positioning
Apply the projection for SVG element placement:
// Example 3: Using the projection inside the visualizer's painter
val currentPoint = timelinePainter.currentPoint // GeoPoint
val mercatorPos = WebMercator.project(currentPoint) // Point used for SVG positioning
svgElement.setAttribute("transform",
"translate(${mercatorPos.x}, ${mercatorPos.y})")
Verification and Testing
The implementation includes comprehensive test coverage to ensure mathematical accuracy. Unit tests in app/src/test/java/dev/mahlernim/timelinevisualizer/model/WebMercatorTest.kt validate the projection behavior against known reference values. Additionally, JavaScript tests in web/src/geo.test.ts (lines 13-20) verify that the client-side rendering logic matches the Kotlin server-side calculations, ensuring consistency across platforms.
Summary
- The Web Mercator projection formula in this repository uses the standard authalic radius of 6,378,137 meters for spherical calculations.
- The
WebMercatorobject inTimelineModels.ktprovides both single-coordinate andGeoPointprojection methods. - X-coordinates calculate as radius multiplied by longitude in radians, while Y-coordinates use the logarithmic tangent transformation.
- The rendering pipeline in
TimelinePainter.ktconsumes these projections to position SVG elements at correct geographic locations. - Test suites in both Kotlin and TypeScript verify projection accuracy across the application stack.
Frequently Asked Questions
What is the exact radius constant used in the Web Mercator projection formula?
The implementation uses RADIUS = 6378137.0 meters, which represents the Earth's authalic radius according to the WGS 84 datum. This constant matches the standard specification used by major web mapping providers like Google Maps and OpenStreetMap.
Why does the visualizer use Web Mercator instead of other projections?
Web Mercator (EPSG:3857) is the de facto standard for web mapping because it preserves angles locally and enables seamless tiling across zoom levels. The visualizer adopts this projection to ensure that timeline points align precisely with the standard map tiles used as the background layer.
How does the projection handle extreme latitudes near the poles?
While the mathematical formula supports latitudes up to approximately 85.05113 degrees, the implementation relies on Kotlin's Math.log and Math.tan functions to handle the asymptotic behavior. The projection becomes increasingly distorted beyond 85 degrees, which is consistent with standard Web Mercator limitations where tiles typically stop at these bounds.
Where can I find the unit tests for the projection logic?
The primary unit tests reside in app/src/test/java/dev/mahlernim/timelinevisualizer/model/WebMercatorTest.kt, which validates the Kotlin implementation. JavaScript-side verification exists in web/src/geo.test.ts, ensuring that the projection behaves identically across the Kotlin backend and TypeScript frontend components.
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 →