Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Projections

Why your map looks squashed, and the mistakes that give wrong numbers silently

Eindhoven University of Technology

The Earth is round and your screen is flat. A projection is the rule for flattening one onto the other, and every rule distorts something: area, shape, distance or direction. A coordinate reference system (CRS) is a projection plus the details needed to make coordinates unambiguous: the model of the Earth’s shape, the origin, the units.

You do not need the mathematics. You need to know three CRSs, how to check which one your data is in, and the two mistakes that cause almost every projection bug.

The three you will meet

CRSNameUnitsUse it for
EPSG:4326WGS 84, “lon/lat”degreesStoring and exchanging data. GPS, GeoJSON and most web APIs use it. Not for measuring or for a final map.
EPSG:28992Amersfoort / RD NewmetresAnything in the Netherlands: measuring, buffering, and the final map. All Dutch government data (PDOK, CBS, Kadaster) is published in it.
EPSG:3857Web Mercator“metres”Web map tiles (OpenStreetMap, Google Maps). Fine for looking, wrong for measuring: at Eindhoven’s latitude its distances are about 60 % too long.

An EPSG code is just a catalogue number for a CRS. You can look any of them up at https://epsg.io.

Checking and converting

The CRS is stored with the data. Always print it after reading a file: it is the one line that saves the most debugging time.

<Geographic 2D CRS: EPSG:4326> Name: WGS 84 Axis Info [ellipsoidal]: - Lat[north]: Geodetic latitude (degree) - Lon[east]: Geodetic longitude (degree) Area of Use: - name: World. - bounds: (-180.0, -90.0, 180.0, 90.0) Datum: World Geodetic System 1984 ensemble - Ellipsoid: WGS 84 - Prime Meridian: Greenwich

There are two different operations, and confusing them is the most common projection bug:

lon/lat: [ 5.357 51.4    5.549 51.497]
RD New:  [152881. 378995. 166241. 389787.]

RD coordinates are metres from an origin in northern France, chosen so every point in the Netherlands has positive x and y, and x is always smaller than y. If you see x around 160 000 and y around 380 000, you are looking at Eindhoven in RD New.

What goes wrong in degrees

The map is squashed

A degree of longitude gets shorter as you move away from the equator. At 51.4° N it is only about 62 % of a degree of latitude. Plot lon/lat as if both were the same length and the map is stretched east–west.

<Figure size 1000x450 with 2 Axes>

GeoPandas and sf correct the aspect ratio for lon/lat data when they know the CRS, so you will mostly see this in other plotting tools, in data with a missing CRS, and in maps made by AI assistants that build the plot from raw columns.

Distances and areas are meaningless

This one gives no visual warning at all.

Area in EPSG:4326: 0.0116 square degrees
Area in EPSG:28992: 89.4 km²

GeoPandas warns about this. Many tools do not. The same goes for buffers: a “buffer of 0.01” in lon/lat reaches 1.1 km north and south but only 0.7 km east and west.

<Figure size 450x450 with 1 Axes>

The rule is always the same: project first, then measure.

Lat/lon swapped

GeoJSON, shapely, GeoPandas and sf all use (x, y), which means (longitude, latitude). People, Google Maps and many spreadsheets write (latitude, longitude). Swap them and Eindhoven (5.48° E, 51.44° N) lands in the Indian Ocean off Somalia (51.44° E, 5.48° N). If your points end up in the sea near the equator, this is why.

Making points from a table

A CSV with coordinates is not spatial data until you say which columns hold x and y and which CRS the numbers are in.

Python
R
import pandas as pd

df = pd.read_csv("sensors.csv")                      # columns: id, lon, lat, value
pts = gpd.GeoDataFrame(df, geometry=gpd.points_from_xy(df["lon"], df["lat"]), crs=4326)
pts = pts.to_crs(28992)

If the columns are already RD coordinates (x, y in metres), pass crs=28992 and skip the to_crs.

Projections for the rest of the world

Outside the Netherlands, pick the projection by what the map has to get right:

The map mustChooseExamples
compare areas (choropleths, density)equal-areaEqual Earth (EPSG:8857) for the world; LAEA Europe (EPSG:3035) for Europe
show local shapes and distancesconformal, localthe national grid (RD New, British National Grid, Lambert 93), or the right UTM zone
look familiar on the webWeb Mercator (EPSG:3857)tiled basemaps. Never for comparing areas.

For a world map, Equal Earth is a good default. For Europe, EPSG:3035 is what Eurostat uses.

Seeing the distortion

The classic way to see what a projection does is to draw identical circles on the globe, 2000 km across each, and project them. Where the circles grow, the projection exaggerates area. Where they flatten, it distorts shape. This needs no data at all, so it is a good start for Day 22, Projections.

<Figure size 1300x420 with 3 Axes>

In Web Mercator the circles at 60° are four times the area of those at the equator, which is why Greenland looks the size of Africa. Equal Earth keeps every circle the same area, at the price of squashing their shape near the edges. No flat map can keep both.

R
library(sf)

world <- rnaturalearth::ne_countries(returnclass = "sf")   # install.packages("rnaturalearth")
plot(st_geometry(st_transform(world, "EPSG:8857")))        # Equal Earth
plot(st_geometry(st_transform(world, "+proj=ortho +lat_0=51.4 +lon_0=5.5")))   # a globe over Eindhoven

Any PROJ string works in st_transform, which makes it easy to try unusual projections for Day 22.