Geographic and polar plots

ProPlot includes features for working with polar axes and the cartopy and basemap map projection packages. These features are optional; installation of cartopy and basemap are not required.

To change the axes projection, pass proj='name' to subplots. To use different projections for different subplots, pass a dictionary of projection names with the subplot number as the key – for example, proj={1:'name'}. The default “projection” is always XYAxes.

Polar projections

To draw polar axes, pass proj='polar' or e.g. proj={1:'polar'} to subplots. This generates a PolarAxes instance. Its format command permits polar-specific modifications like changing the central radius, the zero azimuth location, the radial and azimuthal limits, and the positive azimuthal direction. A demonstration is below.

import proplot as plot
import numpy as np
f, axs = plot.subplots([[1, 1, 2, 2], [0, 3, 3, 0]], proj='polar')
state = np.random.RandomState(51423)
N = 200
x = np.linspace(0, 2*np.pi, N)
y = 100*(state.rand(N, 5)-0.3).cumsum(axis=0)/N
for i in range(5):
    axs.plot(x + i*2*np.pi/5, y[:, i], cycle='contrast', zorder=0, lw=3)
axs.format(suptitle='Polar axes demo', linewidth=1,
           ticklabelsize=9, rlines=0.5, rlim=(0, 19))
axs[0].format(title='Normal plot', thetaformatter='pi', rlines=5, gridalpha=1, gridlinestyle=':',
              rlabelpos=180, color='gray8', ticklabelweight='bold')
axs[1].format(title='Sector plot', thetadir=-1, thetalines=90, thetalim=(0, 270), theta0='N',
              rlim=(0, 22), rlines=5)
axs[2].format(title='Annular plot', thetadir=-1, thetalines=10,
              r0=0, rlim=(10, 22), rformatter='null', rlocator=2)
axs.format(titlepad='1.5em')  # matplotlib default title offset is incorrect

Geographic projections

To specify a geographic projection, pass proj='name' or e.g. proj={1:'name'} to subplots where 'name' is any valid PROJ projection name listed in the Proj table. This generates a GeoAxes or BasemapAxes, depending on whether you passed basemap=True to subplots.

  • GeoAxes joins the cartopy GeoAxes class with the ProPlot Axes class and adds a format command. This class includes all the normal GeoAxes methods, and its format method can be used to set the map bounds with set_extent and add geographic features with add_feature.

  • BasemapAxes redirects the plot, scatter, contour, contourf, pcolor, pcolormesh, quiver, streamplot, and barb methods to identically named methods on the Basemap instance, and provides access to Basemap geographic plotting commands like fillcontinents via the format command.

So with ProPlot, you no longer have to invoke verbose cartopy Projection classes like LambertAzimuthalEqualArea, and you never have to directly reference the Basemap instance – ProPlot works with the Basemap instance under the hood.

To pass keyword args to Basemap and Projection, use the proj_kw dictionary. To make things a bit more consistent, ProPlot lets you supply native PROJ keyword names to the cartopy Projection classes, e.g. lon_0 instead of central_longitude. ProPlot also lets you instantiate Basemap projections with sensible defaults from the basemap_kwargs dictionary, rather than raising an error when certain projection arguments are omitted.

import proplot as plot
plot.rc.coastlinewidth = plot.rc.linewidth = 0.8
f, axs = plot.subplots(ncols=2, axwidth=2.5,
                       proj='robin', proj_kw={'lon_0': 180})
axs.format(suptitle='Simple projection axes demo',
           coast=True, latlines=30, lonlines=60)
/home/docs/checkouts/ DownloadWarning: Downloading:
  warnings.warn('Downloading: {}'.format(url), DownloadWarning)
import proplot as plot
f, axs = plot.subplots(
    hratios=(1.5, 1, 1, 1, 1),
    basemap={(1, 3, 5, 7, 9): False,
             (2, 4, 6, 8, 10): True},
    proj={(1, 2): 'mill', (3, 4): 'cyl', (5, 6): 'moll',
          (7, 8): 'sinu', (9, 10): 'npstere'},
    ncols=2, nrows=5)  # , proj_kw={'lon_0':0})
axs.format(suptitle='Complex projection axes demo')
axs.format(coast=True, latlines=30, lonlines=60)
axs[:, 1].format(labels=True, lonlines=plot.arange(-180, 179, 60))
axs.format(collabels=['Cartopy examples', 'Basemap examples'])
Warning: Cannot label meridians on Mollweide basemapWarning: Cannot label meridians on Sinusoidal basemap

Included projections

The available cartopy and basemap projections are plotted below. See Proj for a table of projection names with links to the relevant PROJ documentation.

ProPlot uses the cartopy API to add the Aitoff, Hammer, Winkel Tripel, and Kavrisky VII projections (i.e. 'aitoff', 'hammer', 'wintri', and 'kav7'), as well as North and South polar versions of the Azimuthal Equidistant, Lambert Azimuthal Equal-Area, and Gnomic projections (i.e. 'npaeqd', 'spaeqd', 'nplaea', 'splaea', 'npgnom', and 'spgnom'), modeled after the existing NorthPolarStereo and SouthPolarStereo projections.

Note that while cartopy projection bounds can be any shape, basemap projection bounds are usually rectangles. Basemap used to have many more projections than cartopy, but the ProPlot additions have evened things out.

import proplot as plot
import numpy as np
projs = ['cyl', 'merc', 'mill', 'lcyl', 'tmerc',
         'robin', 'hammer', 'moll', 'kav7', 'aitoff', 'wintri', 'sinu',
         'geos', 'ortho', 'nsper', 'aea', 'eqdc', 'lcc', 'gnom',
         'npstere', 'nplaea', 'npaeqd', 'npgnom', 'igh',
         'eck1', 'eck2', 'eck3', 'eck4', 'eck5', 'eck6']
f, axs = plot.subplots(ncols=3, nrows=10, proj=projs)
axs.format(land=True, reso='lo', labels=False,
           suptitle='Table of cartopy projections')
for proj, ax in zip(projs, axs):
    ax.format(title=proj, titleweight='bold', labels=False)
/home/docs/checkouts/ DownloadWarning: Downloading:
  warnings.warn('Downloading: {}'.format(url), DownloadWarning)
/home/docs/checkouts/ UserWarning: Unable to determine extent. Defaulting to global.
  warnings.warn('Unable to determine extent. Defaulting to global.')
import proplot as plot
import numpy as np
projs = ['cyl', 'merc', 'mill', 'cea', 'gall', 'sinu',
         'eck4', 'robin', 'moll', 'kav7', 'hammer', 'mbtfpq',
         'geos', 'ortho', 'nsper',
         'vandg', 'aea', 'eqdc', 'gnom', 'cass', 'lcc',
         'npstere', 'npaeqd', 'nplaea']
f, axs = plot.subplots(ncols=3, nrows=8, basemap=True, proj=projs)
axs.format(land=True, labels=False, suptitle='Table of basemap projections')
for proj, ax in zip(projs, axs):
    ax.format(title=proj, titleweight='bold', labels=False)

Zooming into projections

To zoom into cartopy projections, you can use set_extent, or alternatively pass lonlim, latlim, or boundinglat to format. The boundinglat controls the circular boundary extent for North Polar and South Polar Stereographic, Azimuthal Equidistant, Lambert Azimuthal Equal-Area, and Gnomonic projections (ProPlot makes sure these projections always have circular bounds).

To zoom into basemap projections, you must pass the limits to the Basemap class directly by passing proj_kw to subplots with any of the boundinglat, llcrnrlon, llcrnrlat, urcrnrlon, urcrnrlat, llcrnrx, llcrnry, urcrnrx, urcrnry, width, or height keyword args.

import proplot as plot
f, axs = plot.subplots(
    nrows=2, axwidth=4.5,
    proj='pcarree', basemap={1: False, 2: True},
    proj_kw={2: {'llcrnrlon': -20, 'llcrnrlat': -10, 'urcrnrlon': 180, 'urcrnrlat': 50}})
# Ordinary projection
    land=True, labels=True, lonlines=20,
    latlines=20, suptitle='Zooming into projections')
axs[0].format(lonlim=(-140, 60), latlim=(-10, 50),
              labels=True, title='Cartopy example')
axs[1].format(title='Basemap example')
# Polar projection
f, axs = plot.subplots(
    ncols=2, axwidth=2.2,
    proj={1: 'splaea', 2: 'npaeqd'}, basemap={1: False, 2: True},
    proj_kw={2: {'boundinglat': 60}})
    land=True, latlines=10, latmax=80,
    suptitle='Zooming into polar projections')
axs[0].format(boundinglat=-60, title='Cartopy example')
axs[1].format(title='Basemap example')
# Example from basemap website
f, axs = plot.subplots(
    ncols=2, axwidth=2, proj='lcc', basemap={1: False, 2: True},
    proj_kw={1: {'lon_0': 0}, 2: {'lon_0': -100, 'lat_0': 45, 'width': 8e6, 'height': 8e6}})
axs.format(suptitle='Zooming into specific regions', land=True)
    title='Cartopy example', land=True,
    lonlim=(-20, 50), latlim=(30, 70))
axs[1].format(title='Basemap example', land=True)

Plotting geophysical data

The below example demonstrates how to plot geophysical data with ProPlot. It is mostly the same as cartopy, but with some new features powered by the standardize_2d, default_transform, and default_latlon wrappers.

  • For both basemap and cartopy projections, you can pass globe=True to 2D plotting commands to ensure global data coverage.

  • For GeoAxes plotting methods, transform=crs.PlateCarree() is now the default behavior. That is, ProPlot assumes your data is in longitude-latitude coordinates rather than map projection coordinates.

  • For BasemapAxes plotting methods, latlon=True is now the default behavior. Again, plotting methods are now called on the axes instead of the Basemap instance.

import proplot as plot
import numpy as np
offset = -40
x = plot.arange(offset, 360 + offset-1, 60)
y = plot.arange(-60, 60+1, 30)
state = np.random.RandomState(51423)
data = state.rand(len(y), len(x))
titles = ('Geophysical data demo', 'Global coverage demo')
for globe in (False, True,):
    f, axs = plot.subplots(
        ncols=2, nrows=2, axwidth=2.5,
        proj='kav7', basemap={(1, 3): False, (2, 4): True})
    for i, ax in enumerate(axs):
        cmap = ('sunset', 'sunrise')[i % 2]
        if i < 2:
            m = ax.contourf(x, y, data, cmap=cmap, globe=globe, extend='both')
            f.colorbar(m, loc='b', span=i+1, label='values',
                       tickminor=False, extendsize='1.7em')
            ax.pcolor(x, y, data, cmap=cmap, globe=globe, extend='both')
        if globe:
        ix = offset + np.linspace(0, 360, 20)
        for cmd in (np.sin, np.cos):
            iy = cmd(ix*np.pi/180)*60
            ax.plot(ix, iy, color='k', lw=0, marker='o')
               collabels=['Cartopy example', 'Basemap example'],
               rowlabels=['Contourf', 'Pcolor'], latlabels='r', lonlabels='b', lonlines=90,
               abc=True, abcstyle='a)', abcloc='ul', abcborder=False)
/home/docs/checkouts/ ProPlotWarning: Cannot add gridline labels to cartopy KavrayskiyVII projection.
/home/docs/checkouts/ ProPlotWarning: Cannot add gridline labels to cartopy KavrayskiyVII projection.

Customizing projections

GeoAxes and BasemapAxes both derive from ProjAxes, which provides a format method. format can be used to draw gridlines, add gridline labels, set gridline label locations, modify the projection bounding box, and add and stylize geographic features, like land masses, coastlines, and international borders. This method also calls format on Axes, and so can be used for subplot titles, a-b-c labels, and figure titles as before.

import proplot as plot
f, axs = plot.subplots(
    [[1, 1, 2], [3, 3, 3]],
    axwidth=4, proj={1: 'robin', 2: 'ortho', 3: 'wintri'})
ax = axs[0]
ax.format(title='Robinson map', land=True, landcolor='navy blue', facecolor='pale blue',
          coastcolor='gray5', borderscolor='gray5', innerborderscolor='gray5',
          geogridlinewidth=1, geogridcolor='gray5', geogridalpha=1,
          coast=True, innerborders=True, borders=True)
ax = axs[1]
ax.format(title='Ortho map', reso='med', land=True, coast=True, latlines=10, lonlines=15,
          landcolor='mushroom', suptitle='Projection axes formatting demo',
          facecolor='petrol', coastcolor='charcoal', coastlinewidth=0.8, geogridlinewidth=1)
ax = axs[2]
ax.format(land=True, facecolor='ocean blue', landcolor='almond', title='Winkel tripel map',
          lonlines=60, latlines=15)
axs.format(suptitle='Projection axes formatting demo', collabels=['col 1', 'col 2'],
           abc=True, abcstyle='A.', abcloc='ul', abcborder=False, linewidth=1.5)
/home/docs/checkouts/ DownloadWarning: Downloading:
  warnings.warn('Downloading: {}'.format(url), DownloadWarning)
/home/docs/checkouts/ DownloadWarning: Downloading:
  warnings.warn('Downloading: {}'.format(url), DownloadWarning)
/home/docs/checkouts/ DownloadWarning: Downloading:
  warnings.warn('Downloading: {}'.format(url), DownloadWarning)
/home/docs/checkouts/ DownloadWarning: Downloading:
  warnings.warn('Downloading: {}'.format(url), DownloadWarning)