basemap icon indicating copy to clipboard operation
basemap copied to clipboard

[Bug]: Basemap drawgreatcircle generating gaps in Mercator projection

Open glumr opened this issue 1 year ago • 3 comments

Bug summary

I see an issue where drawgreatcircle() produces paths with gaps in with the Mercator projection.

I can see from web searches that others have seen the same issue and suggestions point to gathering the path using computations with other projections.

Code for reproduction

from mpl_toolkits.basemap import Basemap
import numpy as np
import matplotlib.pyplot as plt

# setup mercator map projection.
m = Basemap(llcrnrlon=-180.,llcrnrlat=-65.,\
            urcrnrlon=180.,urcrnrlat=85.,\
            projection='merc',\
            lat_0=40.,lon_0=-20.,lat_ts=20.)
m.drawcoastlines()
m.fillcontinents()

# AMS
amslat = np.float64(52.309)
amslon = np.float64(4.764)
# Seattle
sealat = np.float64(47.449)
sealon = np.float64(-122.309)
# Helsinki
hellat = np.float64(60.320)
hellon = np.float64(24.956)

m.drawgreatcircle(amslon,amslat,sealon,sealat,linewidth=2,color='b')
m.drawgreatcircle(sealon,sealat,hellon,hellat,linewidth=2,color='b')

# draw parallels
m.drawparallels(np.arange(-60,65,20))
# draw meridians
m.drawmeridians(np.arange(-180,180,30))
plt.show()

Actual outcome

Gaps_times2

Expected outcome

Gaps_times5

Additional information

I don't understand the basemap calculations and how projections enter into it but I can recover the correct output if I change the test for cuts in the source code by making max_dist much larger...

diff -c __init*py.orig **init*py
*** __init__.py.orig    Mon Aug 26 08:40:10 2024
--- __init__.py Mon Aug 26 09:28:34 2024
***************
*** 2901,2907 ****
          p = _p[0].get_path()

          # since we know the difference between any two points, we can use this to find wrap arounds on the plot
!         max_dist = 1000*del_s*2

          # calculate distances and compare with max allowable distance
          dists = np.abs(np.diff(p.vertices[:,0]))
--- 2901,2907 ----
          p = _p[0].get_path()

          # since we know the difference between any two points, we can use this to find wrap arounds on the plot
!         max_dist = 1000*del_s*5

          # calculate distances and compare with max allowable distance
          dists = np.abs(np.diff(p.vertices[:,0]))

This is what I did to generate the output in the expected result box. It would be helpful if the defaults did allow great circle paths into high lattitudes.

Operating system

Windows 10

Matplotlib Version

3.8.4

Matplotlib Backend

3.8.4

Python version

3.12.2

Jupyter version

No response

Installation

conda

glumr avatar Aug 26 '24 08:08 glumr

Hi @glumr it looks like this issue is specifically about basemap, so I’ll transfer it to that repository.

rcomer avatar Aug 26 '24 09:08 rcomer

Ir seems this has been noted a long time ago: https://github.com/matplotlib/basemap/issues/171

I initially searched in matplotlib/matplotlib issues so did not find the above.

glumr avatar Aug 26 '24 11:08 glumr

You can create arbitrarily-long great-circle paths on the Mercator projection by making the path arbitrarily close to the pole, so any fixed number will break down in some circumstance. Perhaps the logic should change to take the plot limits into account if the projection can be infinite (Mercator and variants, Lambert Conformal, and Stereographic and variants are the ones that come to mind as relevant for this)?

It might also be possible to make that scale factor a keyword-argument of the function, defaulting to two and only getting used when needed.

DWesl avatar Aug 30 '24 15:08 DWesl