MapMath

CHAPTER 18

Coordinate transforms — converting between CRS

EPSG:4326 ↔ 3857 by hand, using proj4js, and the practical situations where you need to transform coordinates.

4 min read

A coordinate reference system (CRS) defines how coordinates map to a location on Earth. Most data you encounter is in one of two CRS, and converting between them is a daily task.

optional — skip if familiarrefresher

A coordinate reference system (CRS) defines the relationship between coordinates and locations on Earth. Every dataset has one. If two datasets use different CRS, their coordinates are incomparable — like comparing heights in metres and feet without converting. EPSG codes are the standard shorthand for identifying them.

The two you'll use 95% of the time

EPSGNameUnitsUsed by
4326WGS84 geographicDegrees (lat, lon)GPS, GeoJSON, most APIs
3857Web MercatorMetres (x, y)Google Maps, OSM, Mapbox tiles

The distinction matters in practice because operations that assume planar geometry — computing buffer distances, calculating area, overlaying rasters — work correctly in metres but give wrong results in degrees. A degree of longitude at the equator is ~111 km; a degree of longitude at 60°N is ~56 km. Treating them as equal without projection introduces systematic distortion.

EPSG:4326 → EPSG:3857 (degrees → metres)

This is the Mercator projection formula you saw in Chapter 4, applied to the Web Mercator sphere:

x=λRx = \lambda \cdot R y=Rln ⁣(tan ⁣(π4+φ2))y = R \cdot \ln\!\left(\tan\!\left(\tfrac{\pi}{4} + \tfrac{\varphi}{2}\right)\right)

where R=6,378,137R = 6{,}378{,}137 m (WGS84 equatorial radius), and φ, λ are in radians.

const R = 6_378_137; // metres

function toWebMercator(lat, lon) {
  const x = (lon * Math.PI / 180) * R;
  const y = Math.log(Math.tan(Math.PI / 4 + (lat * Math.PI / 180) / 2)) * R;
  return { x, y };
}

EPSG:3857 → EPSG:4326 (metres → degrees)

The inverse:

λ=xR180π\lambda = \frac{x}{R} \cdot \frac{180}{\pi} φ=(2arctan(ey/R)π2)180π\varphi = \left(2\arctan(e^{y/R}) - \frac{\pi}{2}\right) \cdot \frac{180}{\pi}
function fromWebMercator(x, y) {
  const lon = (x / R) * (180 / Math.PI);
  const lat = (2 * Math.atan(Math.exp(y / R)) - Math.PI / 2) * (180 / Math.PI);
  return { lat, lon };
}

CRS converter — EPSG:4326 ↔ EPSG:3857

X (easting)8277717.34 m
Y (northing)3700466.78 m
x = lon × π/180 × R = 74.36 × 0.01745 × 6,378,137

Valid range for Web Mercator

Web Mercator cannot represent the poles. The practical limits are:

  • Longitude: −180° to +180°
  • Latitude: −85.051129° to +85.051129°

At exactly ±90°, the formula produces ±∞ in the y direction. Google Maps and friends clip to ±85.051129° to produce a square world tile.

Using proj4js

For anything beyond 4326 ↔ 3857, use proj4:

npm install proj4
import proj4 from "proj4";

// Define your custom CRS (find definitions at epsg.io)
proj4.defs("EPSG:32642", "+proj=utm +zone=42 +datum=WGS84 +units=m +no_defs");

// Convert Lahore from WGS84 to UTM Zone 42N
const [easting, northing] = proj4("EPSG:4326", "EPSG:32642", [74.3587, 31.5204]);
// → [381453, 3488694]  (metres from zone origin)
Chapter 18 · Paid content

Continue reading "Coordinate transforms — converting between CRS"

You've reached the end of the free preview. Unlock all 22 paid chapters, including distance math, bearings, polygons, spatial indexing, and 3D map rendering — plus a downloadable PDF and the companion code repo.

  • All 22 paid chapters with worked examples
  • Downloadable PDF for offline reading
  • Companion GitHub repo (JavaScript + Python)
  • Free updates for life

Multiple payment options including Wise, PayPal, and bank transfer.