Introduction
If you have been working in seismology, then you must have come across Generic Mapping Tools (GMT) software. It is widely used software, not only in seismology but across the Earth, Ocean, and Planetary sciences and beyond. It is a free, open-source software used to generate publication quality maps or illustrations, process data and make animations. Recently, GMT built API (Application Programming Interface) for MATLAB, Julia and Python. In this post, we will explore the Python wrapper library for the GMP API – PyGMT. Using the GMT from Python script allows enormous capabilities.

The API reference for PyGMT can be accessed from here and is strongly recommended. Although PyGMT project is still in completion, there are many functionalities available.
Step-by-step guide for PyGMT using an example
In this post, we will demonstrate the PyGMT implementation by plotting the topographic map of southern India. We will also plot some markers on the map.
Importing Libraries
The first thing I like to do is to import all the necessary libraries for the task. This keeps the code organized.
import pygmt
minlon, maxlon = 60, 95
minlat, maxlat = 0, 25
Define topographic data source
The topographic data can be accessed from various sources. In this snippet below, I listed a couple of sources.
#define etopo data file
# topo_data = 'path_to_local_data_file'
topo_data = '@earth_relief_30s' #30 arc second global relief (SRTM15+V2.1 @ 1.0 km)
# topo_data = '@earth_relief_15s' #15 arc second global relief (SRTM15+V2.1)
# topo_data = '@earth_relief_03s' #3 arc second global relief (SRTM3S)
Initialize the pyGMT figure
Similar to the matplotlib’s fig = plt.Figure(), PyGMT begins with the creation of Figure instance.
# Visualization
fig = pygmt.Figure()
Define CPT file
# make color pallets
pygmt.makecpt(
cmap='topo',
series='-8000/8000/1000',
continuous=True
)
Plot the high resolution topography from the data source
Now, we provide the topo_data, region and the projection for the figure to plot. The region can also be provided in the form of ISO country code strings, e.g. TW for Taiwan, IN for India, etc. For more ISO codes, check the wikipedia page here. In this example, we used the projection of M4i, which specifies four-inch wide Mercator projection. For more projection options, check here.
#plot high res topography
fig.grdimage(
grid=topo_data,
region=[minlon, maxlon, minlat, maxlat],
projection='M4i'
)
#plot high res topography
fig.grdimage(
grid=topo_data,
region=[minlon, maxlon, minlat, maxlat],
projection='M4i',
frame=True
)

#plot high res topography
fig.grdimage(
grid=topo_data,
region=[minlon, maxlon, minlat, maxlat],
projection='M4i',
shading=True,
frame=True
)

Plot the coastlines/shorelines on the map
Figure.coast can be used to plot continents, shorelines, rivers, and borders on maps. For details, visit pygmt.Figure.coast.
fig.coast(
region=[minlon, maxlon, minlat, maxlat],
projection='M4i',
shorelines=True,
frame=True
)

Plot the topographic contour lines
We can also plot the topographic contour lines to emphasize the change in topography. Here, I used the contour intervals of 4000 km and only show contours with elevation less than 0km.
fig.grdcontour(
grid=topo_data,
interval=4000,
annotation="4000+f6p",
limit="-8000/0",
pen="a0.15p"
)

Plot data on the topographic map
## Generate fake coordinates in the range for plotting
lons = minlon + np.random.rand(10)*(maxlon-minlon)
lats = minlat + np.random.rand(10)*(maxlat-minlat)
# plot data points
fig.plot(
x=lons,
y=lats,
style='c0.1i',
color='red',
pen='black',
label='something',
)
We plot the locations by red cicles. You can change the markers to any other marker supported by GMT, for example a0.1i will produce stars of size 0.1 inches.

Plot colorbar for the topography
Default is horizontal colorbar
# Plot colorbar
fig.colorbar(
frame='+l"Topography"'
)

For vertical colorbar:
# For vertical colorbar
fig.colorbar(
frame='+l"Topography"',
position="x11.5c/6.6c+w6c+jTC+v"
)
We can define the location of the colorbar using the string x11.5c/6.6c+w6c+jTC+v. +v specifies the vertical colorbar.

Output the figure to a file
Similar to matplotlib, PyGMT shows the figure by
# save figure
fig.show() #fig.show(method='external')
To save figure to png. PyGMT crops the figure by default and has output figure resolution of 300 dpi for png and 720 dpi for pdf. There are several other output formats available as well.
# save figure
fig.savefig("topo-plot.png", crop=True, dpi=300, transparent=True)
fig.savefig("topo-plot.pdf", crop=True, dpi=720)
If you want to save all formats (e.g., pdf, eps, tif) then
allformat = 1
if allformat:
fig.savefig("topo-plot.pdf", crop=True, dpi=720)
fig.savefig("topo-plot.eps", crop=True, dpi=300)
fig.savefig("topo-plot.tif", crop=True, dpi=300, anti_alias=True)
Complete Script
import numpy as np
import pygmt
np.random.seed(0)
minlon, maxlon = 60, 95
minlat, maxlat = 0, 25
## Generate fake coordinates in the range for plotting
lons = minlon + np.random.rand(10)*(maxlon-minlon)
lats = minlat + np.random.rand(10)*(maxlat-minlat)
#define etopo data file
topo_data = '@earth_relief_30s' #30 arc second global relief (SRTM15+V2.1 @ 1.0 km)
# Visualization
fig = pygmt.Figure()
# make color pallets
pygmt.makecpt(
cmap='topo',
series='-8000/8000/1000',
continuous=True
)
#plot high res topography
fig.grdimage(
grid=topo_data,
region=[minlon, maxlon, minlat, maxlat],
projection='M4i',
shading=True,
frame=True
)
# plot coastlines
fig.coast(
region=[minlon, maxlon, minlat, maxlat],
projection='M4i',
shorelines=True,
frame=True
)
# plot topo contour lines
fig.grdcontour(
grid=topo_data,
interval=4000,
annotation="4000+f6p",
# annotation="1000+f6p",
limit="-8000/0",
pen="a0.15p"
)
# plot data points
fig.plot(
x=lons,
y=lats,
style='c0.1i',
color='red',
pen='black',
label='something',
)
## Plot colorbar
# Default is horizontal colorbar
fig.colorbar(
frame='+l"Topography"'
)
# save figure as pdf
fig.savefig("topo-plot.pdf", crop=True, dpi=720)
Plot Focal Mechanism on a Map
How would you plot the focal mechanism on a map? This can be simply done in GMT using the command psmeca. But PyGMT do not support psmeca so far. So, we use a workaround. We call the GMT module using the PyGMT’s pygmt.clib.Session class. This we do using the context manager with in Python.
import pygmt
import numpy as np
minlon, maxlon = 70, 100
minlat, maxlat = 0, 35
## Generate fake coordinates in the range for plotting
num_fm = 15
lons = minlon + np.random.rand(num_fm)*(maxlon-minlon)
lats = minlat + np.random.rand(num_fm)*(maxlat-minlat)
strikes = np.random.randint(low = 0, high = 360, size = num_fm)
dips = np.random.randint(low = 0, high = 90, size = num_fm)
rakes = np.random.randint(low = 0, high = 360, size = num_fm)
magnitudes = np.random.randint(low = 5, high = 9, size = num_fm)
#define etopo data file
topo_data = '@earth_relief_30s' #30 arc second global relief (SRTM15+V2.1 @ 1.0 km)
fig = pygmt.Figure()
# make color pallets
pygmt.makecpt(
cmap='topo',
series='-8000/11000/1000',
continuous=True
)
#plot high res topography
fig.grdimage(
grid=topo_data,
region='IN',
projection='M4i',
shading=True,
frame=True
)
fig.coast( region='IN',
projection='M4i',
frame=True,
shorelines=True,
borders=1, #political boundary
)
for lon, lat, st, dp, rk, mg in zip(lons, lats, strikes, dips, rakes, magnitudes):
with pygmt.helpers.GMTTempFile() as temp_file:
with open(temp_file.name, 'w') as f:
f.write(f'{lon} {lat} 0 {st} {dp} {rk} {mg} 0 0') #moment tensor: lon, lat, depth, strike, dip, rake, magnitude
with pygmt.clib.Session() as session:
session.call_module('meca', f'{temp_file.name} -Sa0.2i')
fig.savefig("fm-plot.pdf", crop=True, dpi=720)

Plotting interstation paths between two stations
The following script uses the sel_pair_csv file for the coordinates of the pairs of stations. The sel_pair_csv file is a csv file that is formatted like:
stn1 stlo1 stla1 stn2 stlo2 stla2
0 A002 121.4669 25.1258 B077 120.7874 23.8272
1 A002 121.4669 25.1258 B117 120.4684 24.1324
2 A002 121.4669 25.1258 B123 120.5516 24.0161
3 A002 121.4669 25.1258 B174 120.8055 24.2269
4 A002 121.4669 25.1258 B183 120.4163 23.8759
df_info = pd.read_csv(sel_pair_csv, names=["stn1", "stlo1", "stla1", "stn2", "stlo2", "stla2"], header=None)
event_fig = os.path.join("." , "interstation_map.png")
plot_map = 1
if plot_map:
topo_data = "@earth_relief_15s"
res = "f"
figtitle="Interstation-Paths"
frame = ["a1f0.25", f"WSen+t{figtitle}"]
colormap = "geo"
minlon, maxlon, minlat, maxlat = (
min(df_info["stlo1"].min(),df_info["stlo2"].min()),
max(df_info["stlo1"].max(),df_info["stlo2"].max()),
min(df_info["stla1"].min(),df_info["stla2"].min()),
max(df_info["stla1"].max(),df_info["stla2"].max()),
)
dcoord = 0.5
minlon, maxlon, minlat, maxlat = (
minlon - dcoord,
maxlon + dcoord,
minlat - dcoord,
maxlat + dcoord,
)
fig = pygmt.Figure()
fig.basemap(region=[minlon, maxlon, minlat, maxlat], projection="M6i", frame=frame)
fig.grdimage(
grid=topo_data,
shading=True,
cmap=colormap,
)
fig.coast(
frame=frame,
resolution=res,
shorelines=["1/0.2p,black", "2/0.05p,gray"],
borders=1,
)
fig.plot(
x=df_info["stlo1"].values,
y=df_info["stla1"].values,
style="i10p",
color="blue",
pen="black",
)
fig.plot(
x=df_info["stlo2"].values,
y=df_info["stla2"].values,
style="i10p",
color="blue",
pen="black",
)
print("Plotting paths now...")
for stn1, stn2, stlo1, stla1, stlo2, stla2 in zip(df_info["stn1"].values, df_info["stn2"].values, df_info["stlo1"].values,df_info["stla1"].values,df_info["stlo2"].values,df_info["stla2"].values):
print(f"-->{stn1}-{stn2}")
fig.plot(
x=[stlo1,stlo2],
y=[stla1,stla2],
pen="0.1p,red,-",
straight_line=1,
)
fig.savefig(event_fig, crop=True, dpi=300)

Event-Station Map
import pygmt
import pandas as pd
def plot_event_moll_map(df_info, event, event_fig, clon=None, colormap='geo', topo_data = "@earth_relief_20m"):
'''
Utpal Kumar
2021, March
param df_info: pandas dataframe containing the event and station coordinates (type: pandas DataFrame)
param event: event name (type: str)
param event_fig: output figure name (type str)
param clon: central longitude (type float)
'''
res = "f"
if not colormap:
colormap = "geo"
if not clon:
clon = df_info["stlo"].mean()
proj = f"W{clon:.1f}/20c"
fig = pygmt.Figure()
fig.basemap(region="g", projection=proj, frame=True)
fig.grdimage(
grid=topo_data,
shading=True,
cmap=colormap,
)
fig.coast(
resolution=res,
shorelines=["1/0.2p,black", "2/0.05p,gray"],
borders=1,
)
fig.plot(
x=df_info["stlo"].values,
y=df_info["stla"].values,
style="i2p",
color="blue",
pen="black",
label="Station",
)
fig.plot(
x=df_info["evlo"].values[0],
y=df_info["evla"].values[0],
style="a15p",
color="red",
pen="black",
label="Event",
)
for stlo, stla in zip(df_info["stlo"].values, df_info["stla"].values):
fig.plot(
x=[df_info["evlo"].values[0], stlo],
y=[df_info["evla"].values[0], stla],
pen="red",
straight_line=1,
)
fig.savefig(event_fig, crop=True, dpi=300)
if __name__=="__main__":
event="test_event"
data_info_file = f"data_info_{event}.txt"
event_fig = f"event_map_{event}.png"
df_info = pd.read_csv(data_info_file)
plot_event_moll_map(df_info, event, event_fig)
Here, I used the Mollweide projection but this code can be applied for any other projections with a little tweak.
The first few lines of the data file are:
network,station,channel,stla,stlo,stel,evla,evlo,evdp,starttime,endtime,samplingRate,dist,baz
TW,GWUB,HHZ,24.5059,121.1131,2159.0,-58.5981,-26.4656,164.66,2018-12-11T02:25:32.000002Z,2018-12-11T03:56:31.990002Z,100.0,15445.41,205.14
TW,LXIB,HHZ,24.0211,121.4133,1327.0,-58.5981,-26.4656,164.66,2018-12-11T02:25:32.000000Z,2018-12-11T03:56:31.990000Z,100.0,15409.58,204.75
TW,VCHM,HHZ,23.2087,119.4295,60.0,-58.5981,-26.4656,164.66,2018-12-11T02:25:32.000000Z,2018-12-11T03:56:31.990000Z,100.0,15242.27,205.4
TW,VDOS,HHZ,20.701,116.7306,5.0,-58.5981,-26.4656,164.66,2018-12-11T02:25:32.000000Z,2018-12-11T03:56:31.990000Z,100.0,14871.63,205.6
TW,VNAS,HHZ,10.3774,114.365,2.0,-58.5981,-26.4656,164.66,2018-12-11T02:25:32.000000Z,2018-12-11T03:56:31.990000Z,100.0,13726.26,203.23
TW,VWDT,HHZ,23.7537,121.1412,2578.0,-58.5981,-26.4656,164.66,2018-12-11T02:25:32.000000Z,2018-12-11T03:56:31.990000Z,100.0,15371.08,204.77
TW,VWUC,HHZ,24.9911,119.4492,42.0,-58.5981,-26.4656,164.66,2018-12-11T02:25:32.000000Z,2018-12-11T03:56:31.990000Z,100.0,15420.85,206.25
TW,YD07,HHZ,25.1756,121.62,442.0,-58.5981,-26.4656,164.66,2018-12-11T02:25:32.000000Z,2018-12-11T03:56:31.990000Z,100.0,15534.34,205.2
TW,HOPB,HHZ,24.3328,121.6929,160.0,-58.5981,-26.4656,164.66,2018-12-11T02:25:32.003130Z,2018-12-11T03:56:31.993130Z,100.0,15452.83,204.75
TW,SYNB,HHZ,23.2482,120.9862,2352.0,-58.5981,-26.4656,164.66,2018-12-11T02:25:32.003131Z,2018-12-11T03:56:31.993131Z,100.0,15313.6,204.62
TW,DYSB,HHZ,24.8208,121.4049,1000.0,-58.5981,-26.4656,164.66,2018-12-11T02:25:32.008393Z,2018-12-11T03:56:31.998393Z,100.0,15489.54,205.14
TW,FUSB,HHZ,24.7597,121.5875,690.0,-58.5981,-26.4656,164.66,2018-12-11T02:25:32.008394Z,2018-12-11T03:56:31.998394Z,100.0,15491.23,205.01
TW,HGSD,HHZ,23.4921,121.4239,134.8,-58.5981,-26.4656,164.66,2018-12-11T02:25:32.008393Z,2018-12-11T03:56:31.998393Z,100.0,15356.77,204.5
---

References
- Uieda, L., Wessel, P., 2019. PyGMT: Accessing the Generic Mapping Tools from Python. AGUFM 2019, NS21B–0813.
- Wessel, P., Luis, J.F., Uieda, L., Scharroo, R., Wobbe, F., Smith, W.H.F., Tian, D., 2019. The Generic Mapping Tools Version 6. Geochemistry, Geophys. Geosystems 20, 5556–5564. https://doi.org/10.1029/2019GC008515







Hello, I do believe your website might be having internet browser compatibility issues. Whenever I look at your web site in Safari, it looks fine however, when opening in IE, it has some overlapping issues. I merely wanted to give you a quick heads up! Aside from that, wonderful website!
Wow, wonderful blog layout! How long have you been blogging for? you make blogging look easy. The overall look of your site is magnificent, let alone the content!
My developer is trying to convince me to move to .net from PHP. I have always disliked the idea because of the costs. But he’s tryiong none the less. I’ve been using WordPress on numerous websites for about a year and am nervous about switching to another platform. I have heard fantastic things about blogengine.net. Is there a way I can import all my wordpress posts into it? Any help would be really appreciated!
Heya! I just wanted to ask if you ever have any issues with hackers? My last blog (wordpress) was hacked and I ended up losing a few months of hard work due to no data backup. Do you have any methods to stop hackers?
Terrific post but I was wondering if you could write a litte more on this topic? I’d be very grateful if you could elaborate a little bit more. Kudos!
Keep this going please, great job!
Thanks for every other informative website. The place else may just I am getting that kind of information written in such a perfect approach? I’ve a undertaking that I am simply now working on, and I’ve been on the look out for such information.
I’m gone to convey my little brother, that he should also visit this weblog on regular basis to take updated from latest news update.
I have been browsing online more than 3 hours today, yet I never found any interesting article like yours. It is pretty worth enough for me. Personally, if all web owners and bloggers made good content as you did, the internet will be a lot more useful than ever before.
Wow that was unusual. I just wrote an really long comment but after I clicked submit my comment didn’t show up. Grrrr… well I’m not writing all that over again. Anyways, just wanted to say superb blog!
If some one needs expert view about running a blog after that i propose him/her to visit this weblog, Keep up the good work.
Wow that was strange. I just wrote an incredibly long comment but after I clicked submit my comment didn’t show up. Grrrr… well I’m not writing all that over again. Anyways, just wanted to say fantastic blog!
Great blog here! Also your website loads up fast! What host are you using? Can I get your affiliate link to your host? I wish my site loaded up as quickly as yours lol
Why people still use to read news papers when in this technological globe the whole thing is presented on web?
This website was… how do I say it? Relevant!! Finally I have found something that helped me. Thanks a lot!
Hello! I know this is somewhat off topic but I was wondering which blog platform are you using for this site? I’m getting sick and tired of WordPress because I’ve had issues with hackers and I’m looking at options for another platform. I would be great if you could point me in the direction of a good platform.
What’s up, I check your blogs daily. Your writing style is awesome, keep up the good work!
Excellent post. I was checking continuously this blog and I am impressed! Very useful info specifically the last part 🙂 I care for such information a lot. I was seeking this certain info for a long time. Thank you and best of luck.
I think the admin of this site is genuinely working hard for his web page, because here every stuff is quality based stuff.
Hello, just wanted to tell you, I liked this post. It was helpful. Keep on posting!