Creating Attractive 3D graphics with MATLAB and Python – A Competition!

While I was writing the new book Python Recipes for Earth Sciences, a Python version of my popular textbook MATLAB Recipes for Earth Sciences, difficulties kept cropping up in reproducing the MATLAB graphs. Either it took much longer with Python or the graphics were just less pretty. Of course, it’s up to me, a less experienced Python user, so I invite all Python users to do better!

It is about the 3D representation of the The 1 arc-minute global relief model of the Earth’s surface (ETOPO1) data set, which is discussed and visualized in chapter 7.3 of my book. The ETOPO1 data set is a global database of topographic and bathymetric data on a regular 1 arc-minute grid (about 2 km) (Amante and Eakins 2009). Older ETOPO2 and ETOPO5 global relief grids have been superseded but are still available. ETOPO1 is a compilation of data from a variety of sources. It can be downloaded from the NOAA National Centers for Environmental Information (NCEI) webpage. We can download either the whole-world grids, or custom grids for ice surface or bedrock. 

The MATLAB code to create the figure and published in the book takes 0.77 s to render and display the graphic on a 16-core Mac Pro (2019) with 96 GB physical memory:

ETOPO1 = load('etopo1_data.txt');
ETOPO1 = flipud(ETOPO1);
ETOPO1(ETOPO1 == -32768) = NaN;
[LON,LAT] = meshgrid(30:1/60:60,-20:1/60:20);

tic

figure('Color',[1 1 1]);
axes('Box','on',...
   'View',[20 60],...
   'LineWidth',0.6,...
   'FontSize',8), hold on
surf(LON,LAT,ETOPO1,...
   'SpecularExponent',20,...
   'FaceLighting','phong',...
   'FaceColor','interp',...
   'EdgeColor','none')
light('Style','local',...
   'Position',[145 70 155801]);
title('ETOPO1 Data Set',...
   'FontSize',10)
xlabel('Longitude',...
   'FontSize',8)
ylabel('Latitude',...
   'FontSize',8)
zlabel('Altitude (m asl)',...
   'FontSize',8)
colormap(jet)

toc

print -dpng -r300 etopo1.png

In contrast the Python code below, inspired by a discussion I had on Stackoverflow, takes 121 s to create the graphic on the same computer:

import numpy as np
from matplotlib import cm
import matplotlib.pyplot as plt
from matplotlib.colors import LightSource
import time as time

ETOPO1 = np.loadtxt('etopo1_data.txt')
ETOPO1 = np.flipud(ETOPO1)
lon = np.linspace(30,60,1801)
lat = np.linspace(-20,20,2401)
LON,LAT = np.meshgrid(lon,lat)

starttime = time.time()

plt.figure()
fig, ax2 = plt.subplots(subplot_kw={"projection": "3d"})
ve = 500
ax2.set_box_aspect((1,1,ve/1850))
ls = LightSource(270,45)
rgb = ls.shade(ETOPO1,
cmap=cm.gist_earth,
vert_exag=ve/1850,
blend_mode='hsv')
ax2.plot_surface(LON,LAT,ETOPO1,
rstride=1,
cstride=1,
linewidth=0,
facecolors=rgb,
antialiased=False,
shade=True)
ax2.tick_params(axis='both',
labelsize=6)
ax2.view_init(30,15-90)
ax2.grid(False)

endtime = time.time()

plt.savefig('etopo1_python.png',dpi=300)
plt.show()

print(endtime-starttime)

Can you do it better? Then send me an email with your code and a 300 dpi version of the graphic.

My colleague Norbert Marwan sent me a script which makes use of the excellent PyGMT package. Here is a slightly modified version of Norbert’s script, which is much faster than the script to create the graphics using the Matplotlib package and the output looks much more beautiful:

import numpy as np
import pygmt as pygmt
import xarray as xr
from matplotlib import cm
import matplotlib.pyplot as plt
from matplotlib.colors import LightSource

ETOPO1 = np.loadtxt('etopo1_data.txt')
ETOPO1 = np.flipud(ETOPO1)

lon = np.linspace(30,60,1801)
lat = np.linspace(-20,20,2401)
LON,LAT = np.meshgrid(lon,lat)

ds = xr.DataArray(data=ETOPO1,
    dims=['lat','lon'],
    coords={'lat':lat,'lon':lon})
frame = ['xa1f0.25','ya1f0.25','wSEnZ']
pygmt.makecpt(
    cmap='geo',
    series=f'-6000/4000/10',
    continuous=True)
fig = pygmt.Figure()
fig.grdview(grid=ds,
    perspective=[150,30],
    frame=frame,
    projection='M15c',
    zsize='4c',
    surftype='i',
    plane='-6000+gazure',
    shading=0)
fig.basemap(
    perspective=True,
    rose='jTL+w3c+l+o-2c/-1c')
fig.colorbar(perspective=True,
    position='JMR+o2c/0c+w10c',
    frame=['a2000'])
fig.show()

References

Amante C, Eakins BW (2009) ETOPO1 1 Arc-Minute Global Relief Model: Procedures, Data Sources and Analysis. NOAA Technical Memorandum NESDIS NGDC-24

Trauth, M.H. (2021) MATLAB Recipes for Earth Sciences – Fifth Edition. Springer International Publishing, ~500 p., Supplementary Electronic Material, Hardcover, ISBN: 978-3-030-38440-1.