NCAR / geocat-viz

GeoCAT-viz contains tools to help plot geoscience data, including convenience and plotting functions that are used to facilitate plotting geosciences data with Matplotlib, Cartopy, and other visualization packages.
https://geocat-viz.readthedocs.io
Other
50 stars 19 forks source link

add_lat_lon_tick_labels crashes for PlateCarre(central_longitude=180) #213

Closed senesis closed 9 months ago

senesis commented 9 months ago

Describe the bug add_lat_lon_ticklabels crashes for PlateCarre(central_longitude=180), while there is no issue if central_longitude=0. And by the way, when NOT calling add_lat_lon_ticklabels, the labels doesn't take account of the value of central_longitude !

To Reproduce Execute this code, setting central_longitude first to 0, then to 180. And also one more time commenting out the call to add_lat_lon_ticklabels

import cartopy.crs as ccrs
import matplotlib.pyplot as plt
import cartopy.feature as cfeature
import geocat.viz as gv

fig = plt.figure(figsize=(6, 3))
projection = ccrs.PlateCarree(central_longitude=180)
ax = plt.axes(projection=projection)
ax.add_feature(cfeature.LAND, color='silver',zorder=1)

ax.xaxis.set_visible(True)
ax.yaxis.set_visible(True)

gv.add_lat_lon_ticklabels(ax)

plt.show();

Screenshots Here is the crash report for central_longitude=180.

---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
File /net/nfs/tools/Users/SU/jservon/spirit-2021.11_envs/20240209/lib/python3.10/site-packages/IPython/core/formatters.py:340, in BaseFormatter.__call__(self, obj)
    338     pass
    339 else:
--> 340     return printer(obj)
    341 # Finally look for special method names
    342 method = get_real_method(obj, self.print_method)

File /net/nfs/tools/Users/SU/jservon/spirit-2021.11_envs/20240209/lib/python3.10/site-packages/IPython/core/pylabtools.py:152, in print_figure(fig, fmt, bbox_inches, base64, **kwargs)
    149     from matplotlib.backend_bases import FigureCanvasBase
    150     FigureCanvasBase(fig)
--> 152 fig.canvas.print_figure(bytes_io, **kw)
    153 data = bytes_io.getvalue()
    154 if fmt == 'svg':

File /net/nfs/tools/Users/SU/jservon/spirit-2021.11_envs/20240209/lib/python3.10/site-packages/matplotlib/backend_bases.py:2342, in FigureCanvasBase.print_figure(self, filename, dpi, facecolor, edgecolor, orientation, format, bbox_inches, pad_inches, bbox_extra_artists, backend, **kwargs)
   2336     renderer = _get_renderer(
   2337         self.figure,
   2338         functools.partial(
   2339             print_method, orientation=orientation)
   2340     )
   2341     with getattr(renderer, "_draw_disabled", nullcontext)():
-> 2342         self.figure.draw(renderer)
   2344 if bbox_inches:
   2345     if bbox_inches == "tight":

File /net/nfs/tools/Users/SU/jservon/spirit-2021.11_envs/20240209/lib/python3.10/site-packages/matplotlib/artist.py:95, in _finalize_rasterization.<locals>.draw_wrapper(artist, renderer, *args, **kwargs)
     93 @wraps(draw)
     94 def draw_wrapper(artist, renderer, *args, **kwargs):
---> 95     result = draw(artist, renderer, *args, **kwargs)
     96     if renderer._rasterizing:
     97         renderer.stop_rasterizing()

File /net/nfs/tools/Users/SU/jservon/spirit-2021.11_envs/20240209/lib/python3.10/site-packages/matplotlib/artist.py:72, in allow_rasterization.<locals>.draw_wrapper(artist, renderer)
     69     if artist.get_agg_filter() is not None:
     70         renderer.start_filter()
---> 72     return draw(artist, renderer)
     73 finally:
     74     if artist.get_agg_filter() is not None:

File /net/nfs/tools/Users/SU/jservon/spirit-2021.11_envs/20240209/lib/python3.10/site-packages/matplotlib/figure.py:3175, in Figure.draw(self, renderer)
   3172         # ValueError can occur when resizing a window.
   3174 self.patch.draw(renderer)
-> 3175 mimage._draw_list_compositing_images(
   3176     renderer, self, artists, self.suppressComposite)
   3178 for sfig in self.subfigs:
   3179     sfig.draw(renderer)

File /net/nfs/tools/Users/SU/jservon/spirit-2021.11_envs/20240209/lib/python3.10/site-packages/matplotlib/image.py:131, in _draw_list_compositing_images(renderer, parent, artists, suppress_composite)
    129 if not_composite or not has_images:
    130     for a in artists:
--> 131         a.draw(renderer)
    132 else:
    133     # Composite any adjacent images together
    134     image_group = []

File /net/nfs/tools/Users/SU/jservon/spirit-2021.11_envs/20240209/lib/python3.10/site-packages/matplotlib/artist.py:72, in allow_rasterization.<locals>.draw_wrapper(artist, renderer)
     69     if artist.get_agg_filter() is not None:
     70         renderer.start_filter()
---> 72     return draw(artist, renderer)
     73 finally:
     74     if artist.get_agg_filter() is not None:

File /net/nfs/tools/Users/SU/jservon/spirit-2021.11_envs/20240209/lib/python3.10/site-packages/cartopy/mpl/geoaxes.py:535, in GeoAxes.draw(self, renderer, **kwargs)
    530         self.imshow(img, extent=extent, origin=origin,
    531                     transform=factory.crs, *factory_args[1:],
    532                     **factory_kwargs)
    533 self._done_img_factory = True
--> 535 return super().draw(renderer=renderer, **kwargs)

File /net/nfs/tools/Users/SU/jservon/spirit-2021.11_envs/20240209/lib/python3.10/site-packages/matplotlib/artist.py:72, in allow_rasterization.<locals>.draw_wrapper(artist, renderer)
     69     if artist.get_agg_filter() is not None:
     70         renderer.start_filter()
---> 72     return draw(artist, renderer)
     73 finally:
     74     if artist.get_agg_filter() is not None:

File /net/nfs/tools/Users/SU/jservon/spirit-2021.11_envs/20240209/lib/python3.10/site-packages/matplotlib/axes/_base.py:3064, in _AxesBase.draw(self, renderer)
   3061 if artists_rasterized:
   3062     _draw_rasterized(self.figure, artists_rasterized, renderer)
-> 3064 mimage._draw_list_compositing_images(
   3065     renderer, self, artists, self.figure.suppressComposite)
   3067 renderer.close_group('axes')
   3068 self.stale = False

File /net/nfs/tools/Users/SU/jservon/spirit-2021.11_envs/20240209/lib/python3.10/site-packages/matplotlib/image.py:131, in _draw_list_compositing_images(renderer, parent, artists, suppress_composite)
    129 if not_composite or not has_images:
    130     for a in artists:
--> 131         a.draw(renderer)
    132 else:
    133     # Composite any adjacent images together
    134     image_group = []

File /net/nfs/tools/Users/SU/jservon/spirit-2021.11_envs/20240209/lib/python3.10/site-packages/matplotlib/artist.py:72, in allow_rasterization.<locals>.draw_wrapper(artist, renderer)
     69     if artist.get_agg_filter() is not None:
     70         renderer.start_filter()
---> 72     return draw(artist, renderer)
     73 finally:
     74     if artist.get_agg_filter() is not None:

File /net/nfs/tools/Users/SU/jservon/spirit-2021.11_envs/20240209/lib/python3.10/site-packages/matplotlib/axis.py:1388, in Axis.draw(self, renderer, *args, **kwargs)
   1385     return
   1386 renderer.open_group(__name__, gid=self.get_gid())
-> 1388 ticks_to_draw = self._update_ticks()
   1389 tlb1, tlb2 = self._get_ticklabel_bboxes(ticks_to_draw, renderer)
   1391 for tick in ticks_to_draw:

File /net/nfs/tools/Users/SU/jservon/spirit-2021.11_envs/20240209/lib/python3.10/site-packages/matplotlib/axis.py:1275, in Axis._update_ticks(self)
   1270 """
   1271 Update ticks (position and labels) using the current data interval of
   1272 the axes.  Return the list of ticks that will be drawn.
   1273 """
   1274 major_locs = self.get_majorticklocs()
-> 1275 major_labels = self.major.formatter.format_ticks(major_locs)
   1276 major_ticks = self.get_major_ticks(len(major_locs))
   1277 self.major.formatter.set_locs(major_locs)

File /net/nfs/tools/Users/SU/jservon/spirit-2021.11_envs/20240209/lib/python3.10/site-packages/matplotlib/ticker.py:218, in Formatter.format_ticks(self, values)
    216 """Return the tick labels for all the ticks at once."""
    217 self.set_locs(values)
--> 218 return [self(value, i) for i, value in enumerate(values)]

File /net/nfs/tools/Users/SU/jservon/spirit-2021.11_envs/20240209/lib/python3.10/site-packages/matplotlib/ticker.py:218, in <listcomp>(.0)
    216 """Return the tick labels for all the ticks at once."""
    217 self.set_locs(values)
--> 218 return [self(value, i) for i, value in enumerate(values)]

File /net/nfs/tools/Users/SU/jservon/spirit-2021.11_envs/20240209/lib/python3.10/site-packages/cartopy/mpl/ticker.py:75, in _PlateCarreeFormatter.__call__(self, value, pos)
     71     # Round the transformed value using a given precision for display
     72     # purposes. Transforms can introduce minor rounding errors that
     73     # make the tick values look bad, these need to be accounted for.
     74     f = 1. / self._transform_precision
---> 75     projected_value = round(f * projected_value) / f
     77 else:
     78 
     79     # There is no projection so we assume it is already PlateCarree
     80     projected_value = value

ValueError: cannot convert float NaN to integer

<Figure size 600x300 with 1 Axes>

OS: see #210

Environment see #210

kafitzgerald commented 9 months ago

Thanks again for the note here.

It looks like my recommendation from the prior issue (#210) isn't as robust as I'd hoped and probably not a great way to handle that situation. I'll update the conversation there to reflect this in a moment.

However, gv.set_axes_limits_and_ticks should do the trick as shown below and hopefully be more robust. Setting ticks via the recommended methods in cartopy should also work.

import cartopy.crs as ccrs
import matplotlib.pyplot as plt
import cartopy.feature as cfeature
import geocat.viz as gv

fig = plt.figure(figsize=(6, 3))
projection = ccrs.PlateCarree(central_longitude=180)
ax = plt.axes(projection=projection)
ax.add_feature(cfeature.LAND, color='silver',zorder=1)

gv.set_axes_limits_and_ticks(ax,
                             xticks=[-180,-90,0,90,180],
                             yticks=[-90,0,90])

gv.add_lat_lon_ticklabels(ax)

plt.show()

I'm going to close this issue for now and follow up on the original issue, but please feel free to reopen if you have questions and/or this doesn't address your problem.

senesis commented 9 months ago

From the documentation, gv.add_lat_lon_ticklabels() is a : "Utility function to make plots look like NCL plots by adding latitude, longitude tick labels"

Its baseline use is to avoid having to compute the set of tick locations. So, the solution you propose does not meet that requirement.

So I am afraid that this issue cannot be closed.

kafitzgerald commented 9 months ago

Sorry for the confusion.

Certainly the description and documentation is at the very least is misleading as it stands regarding add_major_minor_ticks and add_lat_lon_ticklabels and the need to set ticks prior to using these functions (as described in your issue #210). Ideally we would address this in the code as well so additional steps are not needed. This should be addressed by that issue.

This particular issue #213 though seemed to relate to the crash/error from the combination of my original suggestion and the projection described above which can be addressed by the updated suggestion here and in #210. My intent with closing this issue was to indicate that this was now a duplicate of #210 rather than its own issue not necessarily that it had been fully addressed.