这篇博文会介绍写 GMT 脚本时需要遵循的一些风格与习惯。本文的目的是,希望遵循本风格指南的 GMT 脚本能够更易读、易改、更健壮、可移植性更高。

使用脚本来执行 GMT 命令

GMT 遵循了 UNIX 的设计思想,将不同的功能分别放在不同的命令中,因而在绘图时需要执行一系列命令。

若使用命令行来执行一系列命令,很容易弄混前一个命令是什么。将所有的绘图命令放在脚本中可以很方便地重复执行一系列命令,以对绘图的细节进行微调。

除非是一两个命令就可以解决的图,否则应一律使用脚本而非命令行。

Windows 下常用的脚本是 bat,Linux 常用的是 Bash、Perl 和 Python。使用什么脚本语言完全依赖于用户个人的需求与喜好,这里以 Bash 脚本为例,其他脚本同理。

#!/bin/bash

psxy ...
pscoast ...
grdraster ...
grdimage ...
psxy ...

不要跨平台写脚本

不要在 Windows 下写 Bash 脚本然后复制到 Linux 下运行;也不要在 Linux 下写 Bat 脚本放在 Windows 下运行。这其中会遇到很多坑,包括但不限于:

  • 默认编码不同,Windows 用 GBK,Linux 用 UTF8;
  • 换行符不同,Windows 用 \r\n ,Linux 用 \n

如果你真的跨平台写了脚本并遇到各种奇怪的问题时,尝试着新建一个文件,然后把脚本重新手敲一遍。

使用变量

脚本不仅仅只是将一系列命令放在一个文件而已。绘图时有很多需要在多个命令中重复使用的东西,比如设置投影方式的 -J 、设置绘图范围的 -R 、文件名 xxx.ps

关于如何使用变量,一般有两种定义方式,这两种方法各有利弊,尚待权衡:

  1. 将参数作为变量的值

    #!/bin/bash
    J=M6i
    R=0/360/-60/60
    B=60/30
    PS=map.ps
    
    pscoast -J$J -R$R -B$B -W1p -A1000 -K > $PS
    psxy -J -R -Sa0.5c -Gred -O >> $PS << EOF
    160 20
    150 30
    EOF
    
  2. 将选项和参数作为变量的值

    #!/bin/bash
    J=-JM6i
    R=-R0/360/-60/60
    B=-B60/30
    PS=map.ps
    
    pscoast $J $R $B -W1p -A1000 -K > $PS
    psxy -J -R -Sa0.5c -Gred -O >> $PS << EOF
    160 20
    150 30
    EOF
    

不要省略参数

GMT 的一个特性是后面的命令可以继承前面命令的一些参数,比如前面的命令中指定了 -JM10c -R0/360/-60/60 ,后面的命令可以直接使用 -J -R 而不用重复给出更多的参数。 这样的设计减少了用户的键入。

省略参数虽然带来了一点点方便,但也可能会造成一些麻烦:

  1. 写 GMT 脚本时由于需要经常修改、增添命令或调整各个命令之间的顺序。在省略了部分参数的情况 下,调整各个命令之间的顺序就可能导致 -J -R 出现在第一个,有时会造成意想不到的错误。
  2. 参数可以省略本质上是因为之前的命令将参数写到了文件 .gmtcommands 中,因而当在同 一个目录里同时运行两个相同或不同的脚本时,两个脚本就会读写同一个 .gmtcommands 文件, 进而可能导致一个脚本读到的内容是另外一个脚本写的。

因而,尽量不要省略参数。相同的参数在多个命令里要写很多遍,这样很麻烦,但是因为前面已经把这些 参数定义成变量了,所以只是多敲了几个字符而已,因此带来的好处可不少。

#!/bin/bash
J=M6i
R=0/360/-60/60
B=60/30
PS=map.ps

pscoast -J$J -R$R -B$B -W1p -A1000 -K > $PS
psxy -J$J -R$R -Sa0.5c -Gred -O >> $PS << EOF
160 20
150 30
EOF

开始与结束

多个绘图命令会将 PS 代码依次写入到一个 PS 文件中。绘图命令的顺序有时会影响到成图的效果, 最常见的例子就是,如果先 pscoastgrdimage,则 grdimage 的效果就会覆盖 pscoast 的效果。因而在绘制一张稍复杂的图时,经常需要在原有的代码中增添、删除或修改 已有命令的顺序,这个时候尤其需要注意 -K-O 以及重定向符号的使用。

下面的代码解决了这个问题:

#!/bin/bash
J=M6i
R=0/360/-60/60
B=60/30
PS=map.ps

# 写入 PS 文件头
psxy -J$J -R$R -T -K > $PS

# 一系列绘图命令
pscoast -J$J -R$R -B$B -W1p -A1000 -K -O >> $PS

# 写入 PS 文件尾
psxy -J$J -R$R -T -O >> $PS

此处使用了专门的两个命令用于开始和结束一个 PS 绘图。这样做的好处在于:中间的所有绘图命令都使用 -K -O >>,不必再考虑这个命令是第一个还是最后一个了,也可以随意删除或修改任何一个命令而不必担心造成其它效果。

因而,实际写绘图脚本时,先把开始和结束这两个命令写对,然后在两个命令的中间写入真正的绘图命令。每新增一个绘图命令,都可以执行一下脚本,以检查绘图效果,若效果正确,则继续添加下一个绘图命令。

使用 SI 单位制

GMT 支持 SI 单位制和 US 单位制,默认是 SI 单位制。由于 GMT 的开发者是美国人,官方的文档使用的是 US 单位制,因而国内的 GMT 用户在学习的过程中也就习惯性地使用了 US 单位制。

实际上,国内用户对于 US 单位制没有太多的概念, -X1i 远远没有 -X2.5c 直观。 SI 单位制是国际标准单位,也是中国人熟悉的单位,使用 SI 单位制会使得微调更简单。

不要依赖于 GMT 的系统设置

你所写的每一个脚本,将来都可能传给后来人使用,可能在任一台机器上使用。要保证脚本每次运行的结果完全一致,并不是一个简单的事情。

不要修改 GMT 系统设置

有些人喜欢使用特定字体,或者喜欢使用特定尺寸的纸张,这可以通过修改 $GMTHOME/share/conf 下的一堆系统配置文件来实现。但是,不要这样做,这会导致脚本在别人的机器上跑出来完全不一样的结果。

不要省略单位

当使用 -JM10 时,GMT 会默认使用当前的系统默认单位(一般来说是 c,也就是厘米),当脚本在另一台系统默认单位为 i 的机器上运行时,绘图的结果会完全不同。

default 文件的使用

不要手动修改 default 文件!

GMT 中提供了 gmtset 命令可以用于修改缺省参数,比如标题的字体、大小等等。该命令会在当前工作目录下生成一个 .gmtdefaults4 文件,进而影响到接下来绘图命令的执行效果。

合理的使用方式如下:

#!/bin/bash

# 用 gmtset 修改默认参数
gmtset BASEMAP_TYPE plain

# 绘图
psxy ...
pscoast ...
psxy ...

# 删除参数文件
rm .gmt*

在脚本的最后 rm .gmt* 至少删除了两个临时文件,一个是 .gmtcommands,其记录了 通用选项的一些信息,另一个是 .gmtdefaults4,记录了当前的缺省参数。

删除这些文件的原因在于:

  • 临时文件,应该删除。
  • 脚本已经执行完毕,不应该遗留下无用的文件。
  • 保留 .gmtdefaults4 文件,可能会导致下次执行脚本时产生不同的效果。例如,脚本中首先使用了默认字体 0,然后绘制了一部分图,再使用 gmtset 修改字体为字体 1,又绘制了一部分图,若忘记删除 .gmtdefaults4 文件,则该文件会成为下次执行脚本时的默认参数文件,导致默认字体变成 1,因而出现不同的绘图效果。

有这样一种可怕的情况:假如你在 $HOME 下执行了 gmtset 命令,然后画了一个简单的图,但是却忘记删除 $HOME 下生成的 .gmtdefaults4 文件,这会影响到其它目录中几乎所有 GMT 脚本的执行效果,而且这个问题很难排查。要避免这种情况的发生需要遵循几个原则:

  1. 尽量不要在 $HOME 下执行 GMT 命令(可能会产生临时文件,难以清理)
  2. 尽量不要使用命令行执行 GMT 命令(因为你很可能会忘记你刚刚执行过哪些命令)
  3. 使用 gmtset 的脚本,最后一定要记得删除 .gmtdefaults4

-P 选项的使用

只有第一个绘图命令中的 -P 选项是起作用的,所以不需要在每个绘图命令里都使用 -P 选项, 当然若是每个绘图命令都使用了 -P 选项也没有问题,只是不够简洁而已。

两种推荐的使用方式:

  1. 在开始 PS 文件时使用该选项:

    #!/bin/bash
    J=M20c
    R=0/360/-60/60
    B=60/30
    PS=map.ps
    
    psxy -J$J -R$R -T -K -P > $PS
    pscoast -J$J -R$R -B$B -W1p -A1000 -K -O >> $PS
    psxy -J$J -R$R -T -O >> $PS
    
  2. 修改 PAGE_ORIENTATION ,不使用 -P 选项

    #!/bin/bash
    J=M20c
    R=0/360/-60/60
    B=60/30
    PS=map.ps
    
    gmtset PAGE_ORIENTATION portrait
    psxy -J$J -R$R -T -K > $PS
    pscoast -J$J -R$R -B$B -W1p -A1000 -K -O >> $PS
    psxy -J$J -R$R -T -O >> $PS
    rm .gmt*
    

不要滥用 - B 选项

-B 选项用于绘制边框并控制边框的绘制效果。

即每个使用 -B 选项的命令都会绘制一次边框,在没有使用 -X-Y 的情况下,多个命令重复使用 -B 选项会绘制多次边框,但由于边框是重合的,所以会看不出来区别。

对于 -B 选项,合理的用法是仅在第一个命令中使用。

verbose 模式

GMT 命令的输出信息常用于在写脚本时判断命令执行是否正确,而在真正执行时过多的输出信息反而会扰乱用户的屏幕输出。合理的使用 verbose 模式的方式有三种:

  1. 写脚本时每个命令都加上 -V 选项,待确认脚本正确无误之后删除所有 -V

  2. 定义 Verbose 变量

    #!/bin/bash
    
    J=M20c
    R=0/360/-60/60
    B=60/30
    PS=map.ps
    V=-V      # 调试时用这个
    #V=       # 调试完成用这个
    
    psxy -J$J -R$R -T -K -P $V > $PS
    pscoast -J$J -R$R -B$B -W1p -A1000 -K -O $V >> $PS
    psxy -J$J -R$R -T -O $V >> $PS
    
  3. 修改缺省参数

    #!/bin/bash
    J=M20c
    R=0/360/-60/60
    B=60/30
    PS=map.ps
    
    gmtset VERBOSE TRUE
    psxy -J$J -R$R -T -K > $PS
    pscoast -J$J -R$R -B$B -W1p -A1000 -K -O >> $PS
    psxy -J$J -R$R -T -O >> $PS
    rm .gmt*
    

从使用上的简洁来看,最简单的是第三种方法。

慎用 - X 和 - Y

使用这两个选项会导致坐标原点的移动。因而使用的时候需要相当慎重。

  1. 除极个别的情况外, -X-Y 选项应该仅在绘制组合图(即一张图多个子图)时使用;
  2. 对于非组合图,也可以在第一个绘图命令中使用 -Xc -Yc 使得整个绘图框架位于纸张的中央;
  3. 不要仅仅为了将某个符号或文字移动到某个位置就使用这两个选项,如果真的有这种需求的话,应该使用绝对坐标 -Xa1c -Ya1c ,其仅影响当前命令的绘图位置。

网格文件后缀

GMT 主要使用 netCDF 格式作为网格数据的格式,其标准后缀名为 .nc

需要注意以下两个事实:

  1. GMT 不会对后缀进行检测,所以后缀是什么都不重要
  2. GMT 之前的版本中曾经自定义了一种网格数据格式,并使用后缀 .grd,因而很多脚本中都使用了 .grd 作为后缀。

修订历史

  • 2014-05-13:初稿;
  • 2014-05-16:关于 “网格文件后缀” 的说明;
  • 2015-03-17:不要跨平台写脚本;
  • 2015-08-08:省略参数可能导致的两种问题;