Reproject with to_crs(3857) only for display, clip geometries to ±85.06° latitude first, and never measure distance or area in Web Mercator — switch to an equal-area or UTM CRS for any calculation.

Jump to heading Why this matters

Almost every web tile map — Folium, Leaflet, Deck.gl, pydeck — renders in Web Mercator (EPSG:3857), while most data is stored and exchanged in WGS 84 (EPSG:4326). So the single most common reprojection in a spatial dashboard is the hop between these two, and it is also the one most often done wrong. Two mistakes dominate: measuring area or distance in 3857, where the numbers are inflated by a latitude-dependent factor, and reprojecting geometries that extend past the projection’s ±85.06° limit, where coordinates blow up to infinity. Doing this hop correctly is the display half of the CRS & coordinate systems reference; the measurement half is covered by looking up EPSG codes for your region.

Jump to heading Prerequisites

Jump to heading Step-by-step solution

Jump to heading Step 1 — Understand why measurement in 3857 is wrong

Web Mercator is conformal: it preserves the shape of small features by stretching the map vertically as you move away from the equator. The vertical scale factor is 1 / cos(latitude), which means a shape at 60° latitude covers roughly four times the area on the map that the same shape covers at the equator. The diagram makes the distortion concrete.

Web Mercator distortion and the valid latitude bandThree equal-area cells on the globe are drawn at the equator, at 45 degrees, and at 70 degrees latitude. In Web Mercator the equator cell keeps its size, the 45 degree cell is enlarged, and the 70 degree cell is enlarged much more, showing that area inflates with latitude. A dashed line marks the 85.06 degree clipping limit beyond which the projection is undefined.EQUAL-AREA TRUTHWEB MERCATOR (3857)0° equator45° lat70° lat×1.0 area≈×2 area≈×8 areavalid band ends at ±85.06° — beyond this, 3857 is undefined

The takeaway: 3857 is a display CRS. Any number you need — a length, an area, a buffer radius — is computed in a different CRS.

Jump to heading Step 2 — Reproject the GeoDataFrame with to_crs

The transform itself is one call. Start from a frame you know is in 4326.

python
import geopandas as gpd
from shapely.geometry import Point

# Plausible points over Berlin and Hamburg (lon, lat)
gdf = gpd.GeoDataFrame(
    {"city": ["Berlin", "Hamburg"]},
    geometry=[Point(13.40, 52.52), Point(9.99, 53.55)],
    crs="EPSG:4326",
)

gdf_web = gdf.to_crs("EPSG:3857")
print(gdf_web.geometry.iloc[0])   # POINT (1491772.6 6894111.9) — metres

Berlin’s (13.40, 52.52) becomes roughly (1,491,773, 6,894,112) in Web Mercator metres. Those large easting/northing values are correct — they are metres from the projection origin, not a bug.

Jump to heading Step 3 — Handle the ±85.06° latitude clipping

Web Mercator’s y coordinate tends to infinity as latitude approaches ±90°, so the projection is cut off at ±85.06°. A dataset with an Arctic vertex — a global coastline, an ice-extent polygon — will produce infinite coordinates after to_crs. Clip to the valid band before reprojecting.

python
from shapely.geometry import box

MERC_LAT = 85.06
valid_band = box(-180, -MERC_LAT, 180, MERC_LAT)

gdf_clipped = gpd.clip(gdf, valid_band)     # drop/trim beyond ±85.06°
gdf_web = gdf_clipped.to_crs("EPSG:3857")

# guard against any surviving infinities
assert gdf_web.geometry.is_valid.all()
assert gdf_web.total_bounds[3] < 2.0e7      # northing stays finite

Jump to heading Step 4 — Measure in an equal-area or UTM CRS

When a filter or tooltip needs an actual area or distance, reproject a copy into the right measurement CRS — never read it from the 3857 frame.

python
# area: use an equal-area CRS
gdf["area_km2"] = gdf.to_crs("EPSG:6933").area / 1_000_000

# distance between the two cities: use a local UTM zone (metres)
metric = gdf.to_crs("EPSG:25832")
d_km = metric.geometry.iloc[0].distance(metric.geometry.iloc[1]) / 1000
print(f"{d_km:.0f} km")   # ~255 km Berlin–Hamburg

Jump to heading Verification

Confirm the round-trip is lossless within the valid band — the fastest proof the transform is correct.

python
import geopandas as gpd
from shapely.geometry import Point

gdf = gpd.GeoDataFrame(
    geometry=[Point(13.40, 52.52)], crs="EPSG:4326"   # Berlin
)
roundtrip = gdf.to_crs("EPSG:3857").to_crs("EPSG:4326")

orig = gdf.geometry.iloc[0]
back = roundtrip.geometry.iloc[0]
assert abs(orig.x - back.x) < 1e-6, "longitude drifted on round-trip"
assert abs(orig.y - back.y) < 1e-6, "latitude drifted on round-trip"
print("round-trip clean")

Jump to heading Edge cases and gotchas

  • Poles clipping: any geometry crossing ±85.06° must be clipped first (Step 3), or a single vertex sends the whole layer’s bounds to infinity and the map fails to fit.
  • Axis order: if points arrive as (lat, lon) instead of (lon, lat), they will land in the wrong hemisphere. Construct Point(lon, lat) and, when using pyproj.Transformer directly, pass always_xy=True.
  • Area in 3857: calling .area on the reprojected frame returns an inflated value that grows with latitude. Always reproject to EPSG:6933, EPSG:3035, or EPSG:5070 for area (Step 4).

Jump to heading FAQ

Why does a polygon look bigger near the poles in Web Mercator?

Web Mercator is a conformal projection that preserves local angles by stretching the map vertically as latitude increases. The scale factor is 1 / cos(latitude), so at 60° a shape is exaggerated about twofold in each direction and roughly fourfold in area. This is a display property, not a data error, but it means you must never compute area directly in EPSG:3857.

What happens to points beyond 85° latitude when reprojecting to 3857?

Web Mercator is only defined up to about ±85.06°, where the projected y coordinate would otherwise go to infinity. Points beyond that latitude produce infinite or extreme values after to_crs. Clip or filter geometries to the valid band before reprojecting so a single polar vertex does not break the whole layer.

Is reprojecting 4326 to 3857 and back lossless?

Within the valid latitude band the round trip is accurate to floating-point tolerance, so an assert with a small tolerance passes. Loss only appears if geometries were clipped at the ±85.06° limit or if coordinates were rounded to reduce precision between the two transforms.


Back to CRS & Coordinate Systems Reference

Related