Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix treatment of spine paths with explicit path-codes #2362

Merged
merged 15 commits into from
Jun 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 9 additions & 6 deletions lib/cartopy/mpl/geoaxes.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@
# CARTOPY_USER_BACKGROUNDS environment variable.
_USER_BG_IMGS = {}


# XXX call this InterCRSTransform
class InterProjectionTransform(mtransforms.Transform):
"""
Expand Down Expand Up @@ -229,7 +228,7 @@ def set_transform(self, transform):
super().set_transform(self._trans_wrap)

def set_boundary(self, path, transform):
self._original_path = path
self._original_path = cpatch._ensure_path_closed(path)
self.set_transform(transform)
self.stale = True

Expand All @@ -239,7 +238,9 @@ def set_path(self, path):

def _adjust_location(self):
if self.stale:
self.set_path(self._original_path.clip_to_bbox(self.axes.viewLim))
self.set_path(
cpatch._ensure_path_closed(
self._original_path.clip_to_bbox(self.axes.viewLim)))
# Some places in matplotlib's transform stack cache the actual
# path so we trigger an update by invalidating the transform.
self._trans_wrap.invalidate()
Expand All @@ -257,14 +258,16 @@ def __init__(self, axes, **kwargs):
super().__init__(axes, 'geo', self._original_path, **kwargs)

def set_boundary(self, path, transform):
self._original_path = path
# Make sure path is closed (required by "Path.clip_to_bbox")
self._original_path = cpatch._ensure_path_closed(path)
self.set_transform(transform)
self.stale = True

def _adjust_location(self):
if self.stale:
self._path = self._original_path.clip_to_bbox(self.axes.viewLim)
self._path = mpath.Path(self._path.vertices, closed=True)
self._path = cpatch._ensure_path_closed(
self._original_path.clip_to_bbox(self.axes.viewLim)
)

def get_window_extent(self, renderer=None):
# make sure the location is updated so that transforms etc are
Expand Down
31 changes: 31 additions & 0 deletions lib/cartopy/mpl/patch.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,37 @@
import shapely.geometry as sgeom


def _ensure_path_closed(path):
"""
Method to ensure that a path contains only closed sub-paths.

Parameters
----------
path
A :class:`matplotlib.path.Path` instance.

Returns
-------
path
A :class:`matplotlib.path.Path` instance with only closed polygons.

"""
# Split path into potential sub-paths and close all polygons
# (explicitly disable path simplification applied in to_polygons)
should_simplify = path.should_simplify
try:
path.should_simplify = False
polygons = path.to_polygons()
finally:
path.should_simplify = should_simplify

codes, vertices = [], []
for poly in polygons:
vertices.extend([poly[0], *poly])
codes.extend([Path.MOVETO, *[Path.LINETO]*(len(poly) - 1), Path.CLOSEPOLY])

return Path(vertices, codes)

def geos_to_path(shape):
"""
Create a list of :class:`matplotlib.path.Path` objects that describe
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
88 changes: 88 additions & 0 deletions lib/cartopy/tests/mpl/test_boundary.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Copyright Crown and Cartopy Contributors
#
# This file is part of Cartopy and is released under the BSD 3-clause license.
# See LICENSE in the root of the repository for full licensing details.

from matplotlib.path import Path
import matplotlib.pyplot as plt
import numpy as np
import pytest

import cartopy.crs as ccrs


circle_verts = np.array([
(21.33625905034713, 41.90051020408163),
(20.260167503134653, 36.31721708143521),
(17.186058185078757, 31.533809612693403),
(12.554340705353228, 28.235578526280886),
(7.02857405747471, 26.895042042418392),
(1.4004024147599825, 27.704250959518802),
(-3.5238590865749853, 30.547274662874656),
(-7.038740387110195, 35.0168098237746),
(-8.701144846177232, 41.42387784534415),
(-7.802741418346137, 47.03850217758886),
(-4.224119444187849, 52.606946662776494),
(0.5099122186645388, 55.75656240544797),
(6.075429329647158, 56.92110282927732),
(11.675092899771812, 55.933731058974054),
(16.50667197715324, 52.93590205611499),
(19.87797456957319, 48.357097196578444),
(21.33625905034713, 41.90051020408163)
raphaelquast marked this conversation as resolved.
Show resolved Hide resolved
])

circle_codes = [
Path.MOVETO,
*[Path.LINETO]*(len(circle_verts)),
Path.CLOSEPOLY
]

rectangle_verts = np.array([
(55.676020408163225, 36.16071428571428),
(130.29336734693877, 36.16071428571428),
(130.29336734693877, -4.017857142857167),
(55.676020408163225, -4.017857142857167),
(55.676020408163225, 36.16071428571428)
])

rectangle_codes = [
Path.MOVETO,
*[Path.LINETO]*(len(rectangle_verts)),
Path.CLOSEPOLY
]


@pytest.mark.natural_earth
@pytest.mark.mpl_image_compare(filename='multi_path_boundary.png')
raphaelquast marked this conversation as resolved.
Show resolved Hide resolved
def test_multi_path_boundary():
offsets = np.array([[30, 30], [70, 30], [110, 20]])

closed = [True, False, False]

vertices, codes = [], []
# add closed and open circles
for offset, close in zip(offsets, closed):
c = circle_verts + offset
if close:
vertices.extend([c[0], *c, c[-1]])
codes.extend([Path.MOVETO, *[Path.LINETO]*len(c), Path.CLOSEPOLY])
else:
vertices.extend([c[0], *c])
codes.extend([Path.MOVETO, *[Path.LINETO]*len(c)])

# add rectangle
vertices.extend(
[rectangle_verts[0], *rectangle_verts, rectangle_verts[-1]]
)
codes.extend(
[Path.MOVETO, *[Path.LINETO]*len(rectangle_verts), Path.CLOSEPOLY]
)

bnds = [*map(min, zip(*vertices)), *map(max, zip(*vertices))]

f, ax = plt.subplots(subplot_kw=dict(projection=ccrs.PlateCarree()))
ax.set_extent((bnds[0], bnds[2], bnds[1], bnds[3]))
ax.coastlines()
ax.set_boundary(Path(vertices, codes))

return f