contextily.add_basemap() is not using correct extend of axis

145 views Asked by At

I want create a matplotlib figure with two subplots of which one is a map. To add a basemap I use contextily. The axis ratio should be aspect="equal" and to ensure that both subplots nonetheless have the same height I set adjustable="datalim" for this axis. This works fine, but contextily does not seem to pick up this change of the axis limits and adds the basemap only to a part of the subplot.

I have prepared this minimal example:

import contextily as cx
import matplotlib.pyplot as plt

fig, axes = plt.subplots(nrows=1,ncols=2)

axes[0].plot([0,1],[0,1],marker="o")
axes[1].plot([7.60,11.13],[51.81,52.10]) 
axes[1].set(aspect="equal", adjustable="datalim")

cx.add_basemap(axes[1],crs="EPSG:4326")

which produces the following plot: minimal example output

1

There are 1 answers

2
raphael On

To ensure that both subplots have the same height I set aspect="equal"

This is not true... the aspect-ratio determines the scale between the x- and y- axis... not the relation between axes in a figure!

If you want the subplots to share the plot-extent, use

fig, axes = plt.subplots(nrows=1, ncols=2, sharex=True, sharey=True)

for further info, see


Update

OK, after your comment I think I get your problem... you want both axes to have the same height while the right axes has to maintain aspect-ratio to avoid image distortions, and in addition you want the right axes to have "tight" limits (e.g. showing only the image without margins)

Using adjustable="datalim" causes the margin and there is no way to avoid this. What you actually want is to use adjustable="box" and then set the axes size to match the desired data limits.

To do that you have to evaluate the desired aspect-ratio and set the axis-position accordingly. The following lines should do the job:

import contextily as cx
import matplotlib.pyplot as plt

fig, (ax0, ax1) = plt.subplots(figsize=(12, 3), nrows=1,ncols=2)

ax0.plot([0,1],[0,1],marker="o")
ax1.plot([7.60,11.13],[51.81,52.10]) 
ax1.set(aspect="equal", adjustable="box")

bbox0 = ax0.get_position()
bbox1 = ax1.get_position()
aspect = bbox1.width / bbox1.height
ax1.set_position((bbox1.x0, bbox0.y0, bbox0.height * aspect, bbox0.height))

cx.add_basemap(ax1, crs="EPSG:4326", zoom=4)

This will result in a figure looking somewhat like this since your map extent is extremely wide... (e.g. you'd need to make the first axes quite small to have them next to each other)

enter image description here

...since the constant re-shuffling of axes (especially together with geo-axes) can be quite tedious, I recommend that you also have a look at the package I'm developing :-D (EOmaps) which provides a basic LayoutEditor to ease the pain of re-arranging axes and maps in a figure... The corresponding code would then look somewhat like this:

from eomaps import Maps

m = Maps(ax=122)
m.set_extent((7.60,11.13, 51.81,52.10), 4326)
m.add_wms.OpenStreetMap.add_layer.stamen_terrain()
m.ax.plot([7.60,11.13],[51.81,52.10], marker="o") 

ax = m.f.add_subplot(121)
ax.plot([0,1],[0,1],marker="o")

layout = {
    "figsize": [12.0, 2.0],
    "0_map": [0.1675, 0.345, 0.81149, 0.4],
    "1_": [0.0375, 0.345, 0.0975, 0.4],
}
m.apply_layout(layout)

enter image description here