diff --git a/lib/cartopy/mpl/geoaxes.py b/lib/cartopy/mpl/geoaxes.py index d63d7150f..a85f0099f 100644 --- a/lib/cartopy/mpl/geoaxes.py +++ b/lib/cartopy/mpl/geoaxes.py @@ -42,7 +42,6 @@ from cartopy.mpl.slippy_image_artist import SlippyImageArtist from cartopy.vector_transform import vector_scalar_to_grid - assert mpl.__version__ >= '1.5.1', ('Cartopy is only supported with ' 'Matplotlib 1.5.1 or greater.') @@ -301,6 +300,7 @@ def set_position(self, position): def _add_transform(func): """A decorator that adds and validates the transform keyword argument.""" + @functools.wraps(func) def wrapper(self, *args, **kwargs): transform = kwargs.get('transform', None) @@ -314,6 +314,7 @@ def wrapper(self, *args, **kwargs): kwargs['transform'] = transform return func(self, *args, **kwargs) + return wrapper @@ -450,7 +451,7 @@ def hold_limits(self, hold=True): self.dataLim.set_points(data_lim) self.viewLim.set_points(view_lim) (self.ignore_existing_data_limits, - self._autoscaleXon, self._autoscaleYon) = other + self._autoscaleXon, self._autoscaleYon) = other def _draw_preprocess(self, renderer): """ @@ -654,7 +655,7 @@ def tissot(self, rad_km=500, lons=None, lats=None, n_samples=80, **kwargs): raise ValueError('lons and lats must have the same shape.') for lon, lat in zip(lons, lats): - circle = geod.circle(lon, lat, rad_km*1e3, n_samples=n_samples) + circle = geod.circle(lon, lat, rad_km * 1e3, n_samples=n_samples) geoms.append(sgeom.Polygon(circle)) feature = cartopy.feature.ShapelyFeature(geoms, ccrs.Geodetic(), @@ -1349,13 +1350,13 @@ def imshow(self, img, *args, **kwargs): # kwargs['alpha'] is guaranteed to be either 1D, 2D, or None alpha = kwargs.pop('alpha') old_img = img[:, :, 0:3] - img = np.zeros(img.shape[:2] + (4, ), dtype=img.dtype) + img = np.zeros(img.shape[:2] + (4,), dtype=img.dtype) img[:, :, 0:3] = old_img # Put an alpha channel in if the image was masked. if not np.any(alpha): alpha = 1 - img[:, :, 3] = np.ma.filled(alpha, fill_value=0) * \ - (~np.any(old_img.mask, axis=2)) + img[:, :, 3] = np.ma.filled(alpha, fill_value=0) * ( + ~np.any(old_img.mask, axis=2)) if img.dtype.kind == 'u': img[:, :, 3] *= 255 @@ -1700,7 +1701,7 @@ def _pcolormesh_patched(self, *args, **kwargs): collection.set_alpha(alpha) collection.set_array(C) if norm is not None: - assert(isinstance(norm, mcolors.Normalize)) + assert (isinstance(norm, mcolors.Normalize)) collection.set_cmap(cmap) collection.set_norm(norm) collection.set_clim(vmin, vmax) @@ -1782,7 +1783,7 @@ def _pcolormesh_patched(self, *args, **kwargs): # projection which will help with curved boundaries size_limit = (abs(self.projection.x_limits[1] - self.projection.x_limits[0]) / - (2*np.sqrt(2))) + (2 * np.sqrt(2))) to_mask = (np.isnan(diagonal0_lengths) | (diagonal0_lengths > size_limit) | np.isnan(diagonal1_lengths) | diff --git a/lib/cartopy/mpl/gridliner.py b/lib/cartopy/mpl/gridliner.py index f45ee2ca2..8f85fd8d3 100644 --- a/lib/cartopy/mpl/gridliner.py +++ b/lib/cartopy/mpl/gridliner.py @@ -714,6 +714,16 @@ def _segment_angle_to_text_specs(self, angle, lonlat): x=dx, y=dy, units='points') kw.update(transform=transform) + if self.xpadding < 0 or self.ypadding < 0: + if kw['ha'] == 'left': + kw['ha'] = 'right' + elif kw['ha'] == 'right': + kw['ha'] = 'left' + if kw['va'] == 'top': + kw['va'] = 'bottom' + elif kw['va'] == 'bottom': + kw['va'] = 'top' + return kw, loc def _update_labels_visibility(self, renderer): @@ -783,6 +793,7 @@ def remove_path_dupes(path): this_patch = artist.get_bbox_patch() this_path = this_patch.get_path().transformed( this_patch.get_transform()) + if '3.1.0' <= matplotlib.__version__ <= '3.1.2': this_path = remove_path_dupes(this_path) center = artist.get_transform().transform_point( @@ -803,6 +814,7 @@ def remove_path_dupes(path): .transformed(self.axes.transData)) if '3.1.0' <= matplotlib.__version__ <= '3.1.2': outline_path = remove_path_dupes(outline_path) + # Inline must be within the map. if ((lonlat == 'lon' and self.x_inline) or (lonlat == 'lat' and self.y_inline)): @@ -810,10 +822,19 @@ def remove_path_dupes(path): # clipping can be left to it. if outline_path.contains_point(center): visible = True - # Non-inline must not run through the outline. - elif not outline_path.intersects_path(this_path): + elif not outline_path.intersects_path(this_path, + False if + self.xpadding < 0 or + self.ypadding < 0 + else True): visible = True + # labels must be within map bbox + if (self.xpadding < 0 or self.ypadding < 0) and ( + not this_path.intersects_path( + outline_path)): + visible = False + # Good if visible: break diff --git a/lib/cartopy/tests/mpl/baseline_images/mpl/test_gridliner/gridliner_labels_intersect_map.png b/lib/cartopy/tests/mpl/baseline_images/mpl/test_gridliner/gridliner_labels_intersect_map.png new file mode 100644 index 000000000..005dbc622 Binary files /dev/null and b/lib/cartopy/tests/mpl/baseline_images/mpl/test_gridliner/gridliner_labels_intersect_map.png differ diff --git a/lib/cartopy/tests/mpl/test_gridliner.py b/lib/cartopy/tests/mpl/test_gridliner.py index 1a52dbf54..9e0ed3cc7 100644 --- a/lib/cartopy/tests/mpl/test_gridliner.py +++ b/lib/cartopy/tests/mpl/test_gridliner.py @@ -129,11 +129,16 @@ def test_gridliner_specified_lines(): TOL = 15 else: TOL = 0.5 -grid_label_tol = grid_label_inline_tol = grid_label_inline_usa_tol = TOL +grid_label_tol = \ + grid_label_inline_tol = \ + grid_label_inline_usa_tol = \ + grid_label_intersect_map_tol = TOL + grid_label_inline_tol += 1.1 grid_label_image = 'gridliner_labels' grid_label_inline_image = 'gridliner_labels_inline' grid_label_inline_usa_image = 'gridliner_labels_inline_usa' +grid_label_intersect_map_image = 'gridliner_labels_intersect_map' @pytest.mark.natural_earth @@ -388,3 +393,51 @@ def test_gridliner_line_limits(): for path in paths: assert (np.min(path.vertices, axis=0) >= (xlim[0], ylim[0])).all() assert (np.max(path.vertices, axis=0) <= (xlim[1], ylim[1])).all() + + +@pytest.mark.natural_earth +@ImageTesting([grid_label_intersect_map_image], + tolerance=grid_label_intersect_map_tol) +def test_grid_labels_intersect_map(): + top = 49.3457868 # north lat + left = -124.7844079 # west long + right = -66.9513812 # east long + bottom = 24.7433195 # south lat + plt.figure(figsize=(35, 35)) + for i, proj in enumerate(TEST_PROJS, 1): + if isinstance(proj, tuple): + proj, kwargs = proj + else: + kwargs = {} + ax = plt.subplot(7, 4, i, projection=proj(**kwargs)) + try: + ax.set_extent([left, right, bottom, top], crs=ccrs.PlateCarree()) + except Exception: + pass + ax.set_title(proj, y=1.075) + if ccrs.PROJ4_VERSION[:2] == (5, 0) and proj in ( + ccrs.Orthographic, + ccrs.AlbersEqualArea, + ccrs.Geostationary, + ccrs.NearsidePerspective, + ): + # Above projections are broken, so skip labels. + # Add gridlines anyway to minimize image differences. + ax.gridlines() + else: + gl = ax.gridlines(draw_labels=True, clip_on=True) + gl.xpadding = -5 + gl.ypadding = -5 + ax.coastlines(resolution="110m") + plt.subplots_adjust(wspace=0.35, hspace=0.35) + + +def test_setting_padding_values(): + plt.figure() + ax = plt.subplot(1, 1, 1, projection=ccrs.PlateCarree()) + gl = ax.gridlines() + assert gl.xpadding == 5 and gl.ypadding == 5 + gl.xpadding = gl.ypadding = 10 + assert gl.xpadding == 10 and gl.ypadding == 10 + gl.xpadding = gl.ypadding = -5 + assert gl.xpadding == -5 and gl.ypadding == -5