How to pick a projection for a map?

When you choose to plot trajectories on a map, you have to make a choice concerning how to represent points at the surface of a sphere (more precisely, an oblate spheroid) on a 2D plane. This transformation is called a projection.

The choice of the right projection depends on the data. The most basic projection (sometimes wrongly referred to as no projection) is the PlateCarree(), when you plot latitude on the y-axis and longitude on the x-axis. The famous Mercator() projection distorts the latitude so as lines with constant bearing appear as straight lines. Conformal projections are also convenient when plotting smaller areas (countries) as they preserve distances (locally).

Many countries define official projections to produce maps of their territory. In general, they fall either in the conformal or in the Transverse Mercator category. Lambert93() projection is defined over France, GaussKruger() over Germany, Amersfoort() over the Netherlands, OSGB() over the British Islands, etc.

The cartes library builds on top of cartopy and tries to provide as many reference projections as possible, to use with Matplotlib or Altair.

Note

Read more about how cartes addresses projections

Tip

If you are happy to be projection illiterate, can’t find the slight differences in the plots below, pick a Mercator() projection. When plotting trajectories over Western Europe, EuroPP() is a decent choice.

Example with Matplotlib

import matplotlib.pyplot as plt

from cartes.crs import Mercator, EuroPP, LambertConformal
from cartes.utils.features import countries

from traffic.data.samples import belevingsvlucht


with plt.style.context("traffic"):
    fig = plt.figure()

    # Choose the projection type
    ax0 = fig.add_subplot(131, projection=Mercator())
    ax1 = fig.add_subplot(132, projection=EuroPP())
    ax2 = fig.add_subplot(133, projection=LambertConformal(10, 45))

    for ax in [ax0, ax1, ax2]:
        ax.add_feature(countries())

        ax.set_extent(belevingsvlucht, buffer=1)
        ax.gridlines()
        ax.spines['geo'].set_visible(False)

        belevingsvlucht.plot(ax)
        ax.set_title(ax.projection.__class__.__name__, fontsize=14)

    fig.set_tight_layout(True)
../_images/projection_0_0.png

Example with Altair

Note

If you do not specify any projection, altair keeps a Mercator projection by default.

import altair as alt
from cartes.atlas import benelux

alt.data_transformers.disable_max_rows()

base = alt.layer(
    alt.Chart(benelux.topo_feature)
    .mark_geoshape(fill="none", stroke="#bab0ac")
    .transform_filter("datum.properties.ISO2 == 'NL'"),
    belevingsvlucht.geoencode(),
    alt.Chart(
        alt.graticule(extent=((3.1, 50.5), (7.7, 54.2)), step=(1, 0.5))
    ).mark_geoshape(stroke="#bab0ac", strokeWidth=0.5),
).properties(width=200)

chart = (
    alt.hconcat(
        base.project().properties(title="Mercator (default)"),
        base.project(**EuroPP()).properties(title="EuroPP"),
        base.project(**LambertConformal(10, 45)).properties(title="LambertConformal"),
    )
    .configure_view(stroke=None)
    .configure_title(anchor="start", font="Lato", fontSize=16)
)
chart