Search code examples
matplotlibcartopy

PyPlot ConnectionPatch between CartoPy GeoAxes


The ConnectionPatch is a useful way to draw a line between two points on two different axes (demo). Is it possible to use this class when one (or both) of the axes is of Cartopy GeoAxes type? A related answer suggests a work-around but I would prefer to avoid this.


Solution

  • I can not answer your question about the use of that class thing. But, if you are interested in plotting the lines between 2 different Cartopy geoaxes, or between matplotlib axes and a geoaxe, that can be achieved with some coordinate transformation. Here is a runnable code and the output plot. I have written some comments within the code to help explain the important steps.

    For further information about coordinate system and tranformation:

    Cartopy https://scitools.org.uk/cartopy/docs/latest/tutorials/understanding_transform.html

    Since Cartopy is built on top of Matplotlib, you need to look into the related subject in Matplotlib.

    Matplotlib https://matplotlib.org/3.2.1/tutorials/advanced/transforms_tutorial.html

    import cartopy
    import cartopy.mpl.geoaxes
    import matplotlib.pyplot as plt
    from mpl_toolkits.axes_grid1.inset_locator import inset_axes
    
    fig, ax = plt.subplots()
    fig.set_size_inches([8,8])  # 9,6; 8,9; 8,3 all OK
    
    # Plot simple line on main axes
    ax.plot([4,5,3,1,2])
    
    p1 = [0.5,3.0]  # Bangkok text location
    p2 = [0.5,2.75] # Himalaya text location
    # Plot texts (Bangkok, Himalaya) on the main axes
    ax.text(*p1, "Bangkok", ha='right')
    ax.text(*p2, "Himalaya", ha='right')
    
    # Ploting on UR inset map (cartopy) on the main axes (ax)
    bkk_lon, bkk_lat = 100, 13       # Bangkok
    hml_lon, hml_lat = 83.32, 29.22  # Everest peak
    
    # Create cartopy geoaxes inset axes as part of the main axes 'ax'
    axins = inset_axes(ax, width="40%", height="30%", loc="upper right", 
                       axes_class = cartopy.mpl.geoaxes.GeoAxes, 
                       axes_kwargs = dict(map_projection = cartopy.crs.PlateCarree()))
    
    # Set map limits on that axes (for Thailand)
    llx, lly = 95, 0
    urx, ury = 110, 25
    axins.set_xlim((llx, urx))
    axins.set_ylim((lly, ury))
    # Plot coastlines
    axins.add_feature(cartopy.feature.COASTLINE)
    
    # Plot line across the inset mao, LL to UR; OK
    #ll_p, ur_p = [llx,urx], [lly,ury]
    #axins.plot(ll_p, ur_p, "r--")
    
    axins.plot(bkk_lon, bkk_lat, 'ro', transform=cartopy.crs.PlateCarree()) # OK!
    
    # Create another inset map on the main axes (ax)
    axins2 = inset_axes(ax, width="40%", height="30%", loc="lower left", 
                   axes_class = cartopy.mpl.geoaxes.GeoAxes, 
                   axes_kwargs = dict(map_projection = cartopy.crs.PlateCarree()))
    
    # Set map limits on that axes (second inset map)
    llx2, lly2 = -60, -20
    urx2, ury2 = 120, 90
    axins2.set_xlim((llx2, urx2))
    axins2.set_ylim((lly2, ury2))
    axins2.add_feature(cartopy.feature.COASTLINE)
    
    # Plot line from UK to BKK, OK
    #p21, p22 = [0, 100], [40, 13]
    #axins2.plot(p21, p22, "r--")
    
    # Plot blue dot at Himalaya
    axins2.plot(hml_lon, hml_lat, "bo")
    
    plt.draw() # Do this to get updated position
    
    # Do coordinate transformation to get BKK, HML locations in display coordinates
    # from axins_data_xy to dp_xy
    dpxy_bkk_axins = axins.transData.transform((bkk_lon, bkk_lat)) # get display coordinates
    # from axins2_data_xy to dp_xy
    dpxy_bkk_axins2 = axins2.transData.transform((hml_lon, hml_lat)) # get display coordinates
    
    # Do coordinate transformation to get BKK, HML locations in data coordinates of the main axes 'ax'
    # from both dp_xy to main_ax_data
    ur_bkk = ax.transData.inverted().transform( dpxy_bkk_axins )
    ll_hml = ax.transData.inverted().transform( dpxy_bkk_axins2 )
    
    # Prep coordinates for line connecting BKK to HML
    xs = ur_bkk[0], ll_hml[0]
    ys = ur_bkk[1], ll_hml[1]
    
    
    xs = ur_bkk[0], ll_hml[0]
    ys = ur_bkk[1], ll_hml[1]
    ax.plot(xs, ys, 'g--') # from Bkk to Himalaya of different inset maps
    
    # Plot lines from texts (on main axes) to locations on maps
    ax.plot([p1[0], ur_bkk[0]], [p1[1], ur_bkk[1]], 'y--')
    ax.plot([p2[0], ll_hml[0]], [p2[1], ll_hml[1]], 'y--')
    
    # Set cartopy inset background invisible
    axins.background_patch.set_visible(False)
    axins2.background_patch.set_visible(False)
    
    plt.show()
    

    The output plot:-

    bkk to hml