gmt icon indicating copy to clipboard operation
gmt copied to clipboard

Function to allow easier annotations outside the map region

Open seisman opened this issue 2 years ago • 3 comments

The original idea comes from my old post (in Chinese) 8 years ago. The recent issue report https://github.com/GenericMappingTools/pygmt/issues/2001 and forum question reminds me this tricky again. So I would like to see if it can be done in PyGMT or even GMT.

The problem

map

If someone wants to plot a map and make annotations outside the map, he will face at least two difficulties:

  1. The annotation is outside the map region. It's impossible to specify its location using a valid geographic coordinate system, although it's possible to use text module's -D option to offset the annotations.
  2. It's difficult to plot a line that crosses the map boundary (related to https://github.com/GenericMappingTools/gmt/issues/5766)

The GMT CLI solution

The solution is actually easy. We just need to define a new, bigger Cartesian frame, write annotations and draw lines in the new frame, and revert to the original frame when finished.

Here is a working solution using GMT CLI:

gmt begin map png
	  gmt coast -Rg -JH20c -Ggray -Slightblue
	  
	  ############################################################
      # Define a Cartesian frame. 
      # Now all locations are given with respect to this frame.
	  gmt plot -R0/25/0/15 -Jx1c -Ba1f1g1 -T
	  echo 21 10 ANNOTATION | gmt text -F+jBL
	  echo 14.5 7.5 21 10 | gmt plot -Sv0.3c+s+be+gblue -W1p,blue
	  # Revert to the original frame
	  gmt plot -Rg -JH20c -T
	  ############################################################

      # now all locations are given with respect to the original geographic frame
	  echo -93 37 | gmt plot -Sa0.5c -Gred
gmt end show

As you can see, here I define a new Cartesian map frame using -R0/25/0/15 -Jx1c, so the frame is 25 cm x 15 cm. I also use -Ba1f1g1 to draw the gridlines so that I can easily determine the location of "annotation" and start/end points of the vector. The intermediate figure looks like this:

map

In the final figure, we don't want to see the gridlines and the Cartesian frame. So we just need to remove -Ba1f1g1. Thus, the final script is:

gmt begin map png
	  gmt coast -Rg -JH20c -Ggray -Slightblue
	  
	  ############################################################
      # Define a Cartesian frame. 
      # Now all locations are given with respect to this frame.
	  gmt plot -R0/25/0/15 -Jx1c -Ba1f1g1 -T
	  echo 21 10 ANNOTATION | gmt text -F+jBL
	  echo 14.5 7.5 21 10 | gmt plot -Sv0.3c+s+be+gblue -W1p,blue
	  # Revert to the original frame
	  gmt plot -Rg -JH20c -T
	  ############################################################

      # now all locations are given with respect to the original geographic frame
	  echo -93 37 | gmt plot -Sa0.5c -Gred
gmt end show

Proposing a new module for GMT

The above syntax is straight forward but it's not elegant, because we still have to revert to the original frame. Perhaps GMT can add a new module (e.g., ruler or annot) to simplify this. With this new module, the new syntax will look like:

gmt begin map png
	  gmt coast -Rg -JH20c -Ggray -Slightblue

	  ############################################################ 
      gmt ruler begin -S25/15 -Ba1f1g1
		echo 21 10 ANNOTATION | gmt text -F+jBL
	  	echo 14.5 7.5 21 10 | gmt plot -Sv0.3c+s+be+gblue -W1p,blue
      gmt ruler end
	  ############################################################

      # now all locations are given with respect to the original geographic frame
	  echo -93 37 | gmt plot -Sa0.5c -Gred
gmt end show

The PyGMT version

The equivalent PyGMT syntax will look like this:

import pygmt

fig = pygmt.Figure()
fig.coast(region="g", projection="H20c", land="gray", water="lightblue")

with fig.ruler(width=25, height=15, showframe=True):
    fig.text(x=21, y=10, text="ANNOTATION")
    fig.plot(data=[14.5, 7.5, 21, 10], style="v0.3c+s+be+gblue", pen="1p,blue")

fig.plot(x=-93, y=37, style="a0.5c", color="red")
fig.show()

It's easy to wrap the proposed GMT's ruler module. If if ruler is not implemented in GMT, the Figure.ruler() function is still doable in PyGMT. The only difficult I can see is how to remember the original map projection and region parameter before entering Figure.ruler, so that we can revert to the original frame when exiting Figure.ruler.

seisman avatar Jul 17 '22 17:07 seisman

It seems @joa-quim also implemented a similar feature in GMT.jl (https://www.generic-mapping-tools.org/GMTjl_doc/examples/misc/mix_paper_geog/) recently.

@PaulWessel Do you think it should be done in GMT or not?

seisman avatar Aug 21 '22 08:08 seisman

Shit, I had written half a page in response and whoops somehow GitHub decided to clear the box...

I will write more later but the essence of my comment was, yes, we need a module with begin/end. My suggestion was gmt cartesian but ruler is OK too. I think paper is to nondescript. I think no clipping is default, and -Cg (clip to geographic area) and -Cr Clip to rectangular area are exclusive options. Not sure if any other option is required?

PaulWessel avatar Aug 22 '22 13:08 PaulWessel

I'm transferring the issue to the upstream GMT repository.

seisman avatar Aug 29 '22 11:08 seisman

Close this?

joa-quim avatar Mar 13 '24 22:03 joa-quim