MapMathMapMath

CHAPTER 18

Coordinate transforms: converting between CRS

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

3 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)-8238755.51 m
Y (northing)4969660.38 m
x = lon × π/180 × R = -74.01 × 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:32618", "+proj=utm +zone=18 +datum=WGS84 +units=m +no_defs");

// Convert New York from WGS84 to UTM Zone 18N
const [easting, northing] = proj4("EPSG:4326", "EPSG:32618", [-74.0060, 40.7128]);
// → [583959, 4507351]  (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: card, Wise, and bank transfer.