Every map requires a projection to represent the 3D Earth on a 2D surface. This process is crucial as it influences the map's appearance and accuracy.
In Python, you can use matplotlib and cartopy
to plot maps with different projections. This post will guide you through the essentials of map projections in Python, including how to change projections, use them in bubble maps, and explore available projection options.
For creating the charts, we will need to load the following libraries:
cartopy
for changing the projectionsimport pandas as pd
import matplotlib.pyplot as plt
import geopandas as gpd
import cartopy.crs as ccrs
# set a higher resolution
plt.rcParams['figure.dpi'] = 300
# load the world dataset
url = "https://raw.githubusercontent.com/holtzy/The-Python-Graph-Gallery/master/static/data/all_world.geojson"
world = gpd.read_file(url)
world.head()
name geometry 0 Fiji MULTIPOLYGON (((180.00000 -16.06713, 180.00000... 1 Tanzania POLYGON ((33.90371 -0.95000, 34.07262 -1.05982... 2 W. Sahara POLYGON ((-8.66559 27.65643, -8.66512 27.58948... 3 Canada MULTIPOLYGON (((-122.84000 49.00000, -122.9742... 4 United States of America MULTIPOLYGON (((-122.84000 49.00000, -120.0000... What's a projection
How can we make a 2D version of a 3D object like the Earth? This challenge is tackled through map projections, which transform the Earth's spherical surface into a flat map. Projections are crucial because they inevitably distort some aspects of the Earth's surface, such as shape, area, distance, or direction.
The choice of projection can significantly alter the message conveyed by a map; for instance, using a Mercator projection preserves direction but distorts the size of landmasses, making regions near the poles appear disproportionately large and potentially misleading viewers about the true scale of countries.
Default mapLet's check how the default matplotlib/geopandas map looks like by using the plot()
method on our previous dataset:
fig, ax = plt.subplots(figsize=(12, 8))
ax.set_axis_off() # remove border around the axes
world.plot(ax=ax)
plt.show()
This projection is called PlateCarree. The default Matplotlib map plot often shows countries with varying shapes. Countries near the equator appear relatively normal, while those farther from the equator, especially near the poles, look distorted.
This occurs because the projection stretches landmasses more as they move away from the equator, causing countries near the poles to spread horizontally along the x-axis.
The mercator projectionOne of the most famous projection is called "Mercator". The Mercator projection maintains accurate direction by representing lines of constant course as straight segments, but it distorts size and shape increasingly towards the poles.
To change our previous chart projection to the Mercator projection, we follow these steps:
projection
variable using the crs
module from cartopy
world
geodataframesubplot_kw
projection = ccrs.Mercator()
world_merc = world.to_crs(projection.proj4_init)
fig, ax = plt.subplots(figsize=(12, 8), subplot_kw={"projection": projection})
ax.set_axis_off() # remove border around the axes
world_merc.plot(ax=ax)
plt.show()
Our map looks way better, but the main problem with Mercator is that, for example, Greenland appears to be larger than Africa when in fact it is 14 times smaller!
Bubble maps and projectionsA common map type is the bubble map, which overlays a scatter plot on a map, using longitudes as x-axis values and latitudes as y-axis values. However, simply changing the map projection doesn’t automatically adjust the scatter plot points accordingly.
Changing the projection alters the representation of the world's shape, thus shifting the actual locations of given latitudes and longitudes. Consequently, we must recalculate the new positions of each scatter plot point. Let's begin by loading a dataset with earthquake positions:
#Load data
url = "https://raw.githubusercontent.com/holtzy/The-Python-Graph-Gallery/master/static/data/earthquakes.csv"
df = pd.read_csv(url)
df = df[df['Depth (km)']>=0.01] # depth of at least 10 meters
df.sort_values(by='Depth (km)', ascending=False, inplace=True)
df.head()
Date Time (utc) Region Magnitude Depth (km) Latitude Longitude Mode Map year 7961 20/02/2019 06:50:47 Banda Sea 5.0 2026 -6.89 129.15 A - 2019.0 6813 07/07/2019 07:50:53 Eastern New Guinea Reg, P.N.G. 5.4 1010 -5.96 147.90 A - 2019.0 8293 17/01/2019 14:01:50 Fiji Islands 4.7 689 -18.65 179.44 A - 2019.0 11258 03/01/2018 06:42:58 Fiji Islands Region 5.5 677 -19.93 -178.89 A - 2018.0 9530 06/09/2018 18:22:24 Fiji Islands Region 5.8 672 -18.88 179.30 A - 2018.0
Now we add the earthquakes on our first map:
fig, ax = plt.subplots(figsize=(12, 8))
ax.set_axis_off()
world.plot(ax=ax)
ax.scatter(
x=df['Longitude'], y=df['Latitude'], s=df['Depth (km)']/3,
color='darkred', alpha=0.3
)
plt.show()
If we want to use the Mercator projection in our bubble map, we have to do multiple things:
projection = ccrs.Mercator()
previous_proj = ccrs.PlateCarree()
new_coords = projection.transform_points(previous_proj, df['Longitude'].values, df['Latitude'].values)
world_merc = world.to_crs(projection.proj4_init)
Everything else stays the same: we just use new_coords
when creating the scatter plot!
projection = ccrs.Mercator()
previous_proj = ccrs.PlateCarree()
new_coords = projection.transform_points(previous_proj, df['Longitude'].values, df['Latitude'].values)
world_merc = world.to_crs(projection.proj4_init)
fig, ax = plt.subplots(figsize=(12, 8), dpi=300, subplot_kw={'projection':projection})
ax.set_axis_off()
world_merc.plot(ax=ax)
# transform the coordinates to the projection's CRS
ax.scatter(
x=new_coords[:, 0], y=new_coords[:, 1], s=df['Depth (km)']/3,
color='darkred', alpha=0.3
)
plt.show()
Other projections
The number of different projections is in fact very large. Below is an exhaustive list of the projections available in cartopy
(the first ones are the most common ones, and recommended for most use cases):z
PlateCarree()
: most basic projectionAlbersEqualArea()
: preserves area, conicalAzimuthalEquidistant()
: preserves distance from centerEquidistantConic()
: preserves distance along meridiansLambertConformal()
: preserves shape, conicalLambertCylindrical()
: equal-area cylindricalMercator()
: preserves direction, exaggerates polesMiller()
: compromise between Mercator and cylindricalMollweide()
: equal-area, ellipticalObliqueMercator()
: rotated Mercator for oblique aspectsOrthographic()
: view from infinite distanceRobinson()
: pseudo-cylindrical, compromiseSinusoidal()
: equal-area, pseudo-cylindricalStereographic()
: conformal, azimuthalTransverseMercator()
: Mercator rotated 90 degreesUTM()
: grid-based, preserves shape locallyInterruptedGoodeHomolosine()
: interrupted equal-areaRotatedPole()
: shifts pole positionOSGB()
: British National GridEuroPP()
: Europe polar stereographicGeostationary()
: view from geostationary orbitNearsidePerspective()
: view from finite distanceEckertI()
: pseudo-cylindrical, equal-areaEckertII()
: pseudo-cylindrical, equal-areaEckertIII()
: pseudo-cylindrical, compromiseEckertIV()
: pseudo-cylindrical, equal-areaEckertV()
: pseudo-cylindrical, compromiseEckertVI()
: pseudo-cylindrical, equal-areaAitoff()
: modified azimuthal, compromiseEqualEarth()
: equal-area, pseudo-cylindricalGnomonic()
: straight great circlesHammer()
: equal-area, ellipticalLambertAzimuthalEqualArea()
: equal-area, azimuthalNorthPolarStereo()
: stereographic centered on North PoleOSNI()
: Northern Ireland GridSouthPolarStereo()
: stereographic centered on South PoleRetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4