3. Map tutorial#

In this notebook, some examples of creating maps in Python using the Cartopy library are shown. We will zoom in from the global scale to the local scale.

import numpy as np
import matplotlib.pyplot as plt
import cartopy.crs as ccrs

3.1. Projections#

A complete list of available projections can be found here. The examples below show how to plot a coarse-resolution coastlines of the entire globe. A list of projections available in Cartopy can be found here: http://scitools.org.uk/cartopy/docs/v0.15/crs/projections.html#

Equirectangular#

This projection just maps longitude to the x-axis and and latitude to the y-axis. It distorts and stretches areas away from the equator.

plt.figure()
ax = plt.axes(projection=ccrs.PlateCarree())
ax.coastlines()
<cartopy.mpl.feature_artist.FeatureArtist at 0x1241e2310>
_images/81d1bca21db1613ae66918488649f0b0320cffbb3d8035fd324d16a426c8ab76.png

Mercator#

The Mercator projection expands latitude and longitude at the same ratio away from the equator. As a result, areas are stretched equally horizontally and vertically such at angles are preserved (i.e. it is conformal). Notice that Greenland and Africa are the same size in this map, which is not true if you look at a globe.

plt.figure()
ax = plt.axes(projection=ccrs.Mercator())
ax.coastlines()
<cartopy.mpl.feature_artist.FeatureArtist at 0x124261010>
_images/e980e8c1b8b84ae225741350e182536f6ab7853318ebcd8dbcef4d30975ed1af.png

Lambert conformal#

This is another projection that is conformal and preserves angles. It is a conic projection, so it is better for displaying large areas at mid-latitudes.

plt.figure()
ax = plt.axes(projection=ccrs.LambertConformal())
gl = ax.gridlines()
ax.coastlines()
<cartopy.mpl.feature_artist.FeatureArtist at 0x1242d1950>
_images/3edc833811c06e1db1c2e7efdbcd76fcc4f49baa730a67785c828a6c1284bd5a.png

3.2. Adding gridlines#

from cartopy.mpl.gridliner import LONGITUDE_FORMATTER, LATITUDE_FORMATTER

plt.figure()
ax = plt.axes(projection=ccrs.Mercator())
gl = ax.gridlines(draw_labels=True)
gl.xformatter = LONGITUDE_FORMATTER
gl.yformatter = LATITUDE_FORMATTER
ax.coastlines()
<cartopy.mpl.feature_artist.FeatureArtist at 0x124320b90>
_images/984a56e06a1eaa64048a27598b728e7875bf1015d10b33aeaac9b7e21c157ab9.png

It can be hard to remember all of these commands. It is easier to make a function. This function is based on a blog post by Filipe Fernandes at https://ocefpaf.github.io/python4oceanographers/blog/2015/06/22/osm/. For a given projection, it returns an axis object that can be used by Cartopy.

def make_map(projection=ccrs.PlateCarree()):
    '''
    Return axis object for cartopy map with labeled gridlines.
    
    Input: Cartopy projection, default = cartopy.crs.PlateCarree()
    Output: Cartopy axis object
    
    Based on blog post by Filipe Fernandes:
    https://ocefpaf.github.io/python4oceanographers/blog/2015/06/22/osm/
    License: Creative Commons Attribution-ShareAlike 4.0
    https://creativecommons.org/licenses/by-sa/4.0/
    
    Example code:
    
    import cartopy.crs as ccrs
    plt.figure()
    ax = make_map(projection=ccrs.Mercator())
    ax.coastlines()
    '''
    ax = plt.axes(projection=projection)
    gl = ax.gridlines(draw_labels=True)
    gl.xlabels_top = False
    gl.ylabels_right = False
    gl.xformatter = LONGITUDE_FORMATTER
    gl.yformatter = LATITUDE_FORMATTER
    return ax

plt.figure()
ax = make_map(ccrs.Mercator())
ax.coastlines()
/Users/tconnolly/opt/miniconda3/envs/ms263-24/lib/python3.11/site-packages/cartopy/mpl/gridliner.py:451: UserWarning: The .xlabels_top attribute is deprecated. Please use .top_labels to toggle visibility instead.
  warnings.warn('The .xlabels_top attribute is deprecated. Please '
/Users/tconnolly/opt/miniconda3/envs/ms263-24/lib/python3.11/site-packages/cartopy/mpl/gridliner.py:487: UserWarning: The .ylabels_right attribute is deprecated. Please use .right_labels to toggle visibility instead.
  warnings.warn('The .ylabels_right attribute is deprecated. Please '
<cartopy.mpl.feature_artist.FeatureArtist at 0x126efea50>
_images/a811f91158bc3235c75f2fb8751add449e91c76e62f719e0e38f8ae0320cd4c2.png

3.3. Higher resolution coastline for smaller areas#

Here we make a map of Monterey Bay. Using the default coastline, it doesn’t work out so well.

# map extent for Monterey Bay
extent = [-122.3, -121.6, 36.5, 37.1]
plt.figure()
ax = make_map(ccrs.Mercator())
ax.set_extent(extent)
ax.coastlines()
/Users/tconnolly/opt/miniconda3/envs/ms263-24/lib/python3.11/site-packages/cartopy/mpl/gridliner.py:451: UserWarning: The .xlabels_top attribute is deprecated. Please use .top_labels to toggle visibility instead.
  warnings.warn('The .xlabels_top attribute is deprecated. Please '
/Users/tconnolly/opt/miniconda3/envs/ms263-24/lib/python3.11/site-packages/cartopy/mpl/gridliner.py:487: UserWarning: The .ylabels_right attribute is deprecated. Please use .right_labels to toggle visibility instead.
  warnings.warn('The .ylabels_right attribute is deprecated. Please '
<cartopy.mpl.feature_artist.FeatureArtist at 0x126f20d50>
_images/93919abd67d7b4d71c9074d8108b60dfddcd6599edcd0eaa0df0bf5d3f1d673f.png

The following code produces a higher resolution coastline. The coastline data sets in Cartopy come from Natural Earth. The ‘10m’ option refers to resolution that is appropriate for a 1:10 million scale map, not 10 meter resolution.

plt.figure()
ax = make_map(ccrs.Mercator())
ax.set_extent(extent)
ax.coastlines('10m')
/Users/tconnolly/opt/miniconda3/envs/ms263-24/lib/python3.11/site-packages/cartopy/mpl/gridliner.py:451: UserWarning: The .xlabels_top attribute is deprecated. Please use .top_labels to toggle visibility instead.
  warnings.warn('The .xlabels_top attribute is deprecated. Please '
/Users/tconnolly/opt/miniconda3/envs/ms263-24/lib/python3.11/site-packages/cartopy/mpl/gridliner.py:487: UserWarning: The .ylabels_right attribute is deprecated. Please use .right_labels to toggle visibility instead.
  warnings.warn('The .ylabels_right attribute is deprecated. Please '
<cartopy.mpl.feature_artist.FeatureArtist at 0x126f757d0>
_images/93919abd67d7b4d71c9074d8108b60dfddcd6599edcd0eaa0df0bf5d3f1d673f.png

We can also easily grab image tiles from the internet to show the same area. This example shows tiles from the familiar Google Maps. You can also show tiles from Stamen Terrain.

import cartopy.io.img_tiles as cimgt
request = cimgt.GoogleTiles()

plt.figure()
ax = make_map(projection=request.crs)
ax.set_extent(extent)
ax.add_image(request,10)
/Users/tconnolly/opt/miniconda3/envs/ms263-24/lib/python3.11/site-packages/cartopy/mpl/gridliner.py:451: UserWarning: The .xlabels_top attribute is deprecated. Please use .top_labels to toggle visibility instead.
  warnings.warn('The .xlabels_top attribute is deprecated. Please '
/Users/tconnolly/opt/miniconda3/envs/ms263-24/lib/python3.11/site-packages/cartopy/mpl/gridliner.py:487: UserWarning: The .ylabels_right attribute is deprecated. Please use .right_labels to toggle visibility instead.
  warnings.warn('The .ylabels_right attribute is deprecated. Please '
_images/4e97924eb91a10269e2ed773a6ecff0db3649ca063ccaa68725b914eee935242.png
import cartopy.io.img_tiles as cimgt
request = cimgt.StamenTerrain()

plt.figure()
ax = make_map(projection=request.crs)
ax.set_extent(extent)
ax.add_image(request,10)
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
Cell In[11], line 2
      1 import cartopy.io.img_tiles as cimgt
----> 2 request = cimgt.StamenTerrain()
      4 plt.figure()
      5 ax = make_map(projection=request.crs)

AttributeError: module 'cartopy.io.img_tiles' has no attribute 'StamenTerrain'

3.4. Using shapefiles#

GIS data often uses the binary shapefile format. Cartopy includes tools that greatly simplify the process of reading these files in Python. To demonstrate, we will map out wetlands and roads near Elkhorn Slough from data contained in GIS shapefiles.

The following code uses data that can be downloaded from the Elkhorn Slough Foundation website: http://www.elkhornslough.org/gis/index.htm

As noted on the site, the shapefiles use a Universal Transverse Mercator Zone 10 projection to convert between distance in meters, and latitude and longitude. They use the WGS84 datum to specify the shape of the ellipsoidal Earth, which is the default in Cartopy. It is important that we specify the same projection when reading the data. Here, we create a custom projection, p.

from cartopy.io import shapereader

wetshp = shapereader.Reader('data/Elkhorn_Slough_GIS/WETLAND/wetland.shp')
p = ccrs.UTM(zone=10)

The data can be displayed by following two steps

  1. Creating a Cartopy axis object, which can be any projection you want (Mercator in this case). This is similar to the plots created earlier.

  2. Looping through the geometries contained in the shapefile, and adding them to the map. Our custom projection must be specified here when reading the shapefile. This ensures that there is a correct conversion from distance in meters (the information contained in the shapefile) to longitude and latitude (which is what we want to plot).

plt.figure()

extent = ([-121.85,-121.65,36.76,36.9])
ax = make_map(projection=ccrs.Mercator())
ax.set_extent(extent)

for geometry in wetshp.geometries():
    try:
        ax.add_geometries(geometry, p, facecolor='white',
                          edgecolor='black')
    except:
        pass
_images/42e33980c9212f2c99869bc95d57ad41cb4912a0fb8e1a60fab164bcfaf67649.png