GenericMappingTools / pygmt

A Python interface for the Generic Mapping Tools.
https://www.pygmt.org
BSD 3-Clause "New" or "Revised" License
747 stars 216 forks source link

Offsets "X" and "Y" provide unexpected results when provided #380

Closed MarkWieczorek closed 3 years ago

MarkWieczorek commented 4 years ago

Everything works fine when making a single plot using grdimage when the offsets X and Y are not provided. Here is an example:

test-no-X-Y-flags

However, if I provide the options X=0, Y=0, the plot is shifted to the lower left, and the left and bottom labels are cut off the plot:

test-0-0

It appears that the left and bottom of the image are perfectly aligned with the bottom of the plot, suggesting that the size of the left and bottom labels has not been taken into account correctly.

seisman commented 4 years ago

This is neither a pygmt bug nor a GMT bug. This is how GMT works. For GMT modern mode, the paper size is about 10 meters by 10 meters, and the plot origin is located at the lower-left corner of the large paper, with offsets of 10 cm in both x and y directions. Giving X=0 and Y=0 means you're shifting the plot origin to the exactly lower-left corner of the paper, and there are no spaces for labels of the left and bottom sides.

MarkWieczorek commented 4 years ago

GMT says the following

-X -Y Shift origin of plot to (, ). Prepend r for shift relative to current point (default), prepend a for temporary adjustment of origin, prepend f to position relative to lower left corner of page, prepend c for offset of center of plot to center of page.

To me, this means that X=0 and Y=0 should not change the origin, because you are adding 0 to the current value.

Could you tell me what the default values of X and Y are if they are not zero?

seisman commented 4 years ago

In classic mode, the default paper size is A4 or US letter. If X and Y options are not used, the default plot origin for a new plot is controlled by these two settings. But, the default values for overlays are 0. image

In modern mode, it's almost the same, but MAP_ORGIN_X and MAP_ORIGIN_Y are hard-coded in the source codes. I remember the defaults values are ~10 cm for modern mode.

seisman commented 4 years ago

Also see the explanations in the cookbook https://docs.generic-mapping-tools.org/latest/cookbook/options.html#plot-positioning-and-layout-the-x-y-options.

seisman commented 4 years ago

I just realized that the link above isn't updated for modern mode. So if you're not familiar with GMT classic mode, it may make you more confused.

MarkWieczorek commented 4 years ago

I confirm that GMT has the same behavior as pygmt, but I am still not convinced that this should be the expected behavior for pygmt. Let me see if I can make my case.

Let's take a method like basemap. In the present configuration, X and Y have different meanings between the first and second call of this method. In the first call

fig = pygmt.Figure()
fig.basesmap(projection='Q0/6i', region=[0, 360, -90, 90], frame='afg', X=0, Y=0)

X and Y set the absolute origin at (0, 0): i.e., the bottom-left of the page. However, if I call basemap again

fig.basesmap(projection='Q0/6i', region=[0, 360, -90, 90], frame='afg', X=0, Y=0)

X and Y shift the origin with respect to the current value. To me, it would be more loigical to do the following:

# create a figure, and set the origin to MAP_ORIGIN_X and MAP_ORIGIN_Y
fig = pygmt.Figure()
# create a figure, and set the map origin to arbitrary values
fig2 = pygmt.Figure(X=10, Y=5)

Then when you call

fig.basesmap(projection='Q0/6i', region=[0, 360, -90, 90], frame='afg', X=0, Y=0)

the X and Y offsets would be relative to the origin specified when creating the figure.

Why do I care about this?

The reason I care is because I am writing my own plotting function that takes as input a preexisting pygmt figure and optional values for the X and Y offsets. The idea was to set the default values of the X and Y offsets to zero, and then pass these to pygmt if these weren't specified. However, with the way pygmt is set up now I would need to set the default offsets to None and then do something like


if xoffset is None:
    fig.basesmap(projection=proj, region=region, frame=frame_str)
else:
    fig.basesmap(projection=proj, region=region, frame=frame_str, X=xoffset)
seisman commented 4 years ago

I agree that the behavior of X and Y is not intuitive. What we can do in pygmt is limited by the core GMT. Currently I don't see a way to implement the behavior similar to what you just described. Let's see if others have better thoughts.

For your use case, a workaround is to set the default offsets to False, then you can always use

fig.basesmap(projection=proj, region=region, frame=frame_str, X=xoffset)
MarkWieczorek commented 4 years ago

As a start, I think that X=False and X=None should return the same result. Now, build_arg_string() returns '-X=None' for the latter, which I would say is a bug. This would solve most problems.

seisman commented 4 years ago

As discussed in #514, the behavior of -X0 and -Y0 is correct. We can do nothing unless GMT changes how -X and -Y works in modern mode. So I believe we can almost close the issue.

As a start, I think that X=False and X=None should return the same result. Now, build_arg_string() returns '-X=None' for the latter, which I would say is a bug. This would solve most problems.

But this comment is interesting. Like True or False, None makes no sense for GMT. build_arg_string() should never returns arguments like '-XNone'.