Skip to content

Use transformed paths for contour labelling decisions #26297

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

Closed
wants to merge 2 commits into from

Conversation

rcomer
Copy link
Member

@rcomer rcomer commented Jul 12, 2023

PR summary

In Cartopy, contour paths can wrap around the edges of the globe. So a path that would be complete in data space can break into segments in screen space. Matplotlib's contour labelling has never accounted for this.

Currently, Cartopy has a workaround where it removes all the paths from each PathCollection, transforms them to axes space, and adds them back in (sometimes as more shorter paths). The changes introduced at #25247 break the workaround (SciTools/cartopy#2207). I tried removing the workaround and found the test image gets a spurious horizontal line around 45 degrees north, where the label is being inserted right on the edge.

result

This PR proposes transforming the paths to screen space before making the decisions about where to put the labels and how to update the paths. With this change (and a smaller font) we now get

result

I see two problems:

  • The back and forth between data and screen space introduced by this change feels a bit hacky, so I would welcome advice on better ways to resolve this.
  • I do not know how to test this without Cartopy.

PR checklist

Copy link
Contributor

@greglucas greglucas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I'm following this update, I think the biggest change here is that you are calling transform_path(path) instead of transform(path.vertices), which for Cartopy is critical because they override transform_path to account for nonlinear transforms, while if we just transform all of the vertices that may be incorrect for a curved path along those vertices.

Would it make sense for Cartopy to trigger the non-linear transform, create all the transformed paths, then use those paths and set the transform to ax.transAxes before calling this as a super class method? Similar to what Cartopy was doing before, but rather than each individual path I'm wondering if you can replace the transform on the object without iterating through all the paths.

Comment on lines +621 to +623
for subpath in trans.transform_path(
self._paths[icon])._iter_connected_components():
screen_xys = subpath.vertices
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you could save the inverse transform below by leaving the for loop as it was, and then calculating tsubpath = trans.transform_path(subpath) inside the loop and then use subpath/tsubpath in the right locations below.

@rcomer
Copy link
Member Author

rcomer commented Jul 14, 2023

Would it make sense for Cartopy to trigger the non-linear transform, create all the transformed paths, then use those paths and set the transform to ax.transAxes before calling this as a super class method?

@greglucas thanks, yes you have nailed it! Solution now at SciTools/cartopy#2213

Everybody else: sorry for the noise!

@rcomer rcomer closed this Jul 14, 2023
@greglucas
Copy link
Contributor

Still might be worth having Matplotlib do the right thing here? I think the current assumption within MPL's contour code is: transform_path(path).vertices == transform(path.vertices) which may not be strictly true as Cartopy is showing.

@rcomer
Copy link
Member Author

rcomer commented Jul 14, 2023

I don't know about the transformed vertices. I think the main issue I'm seeing is because the transformed path has a different set of codes (i.e. it has more MOVETOs). But I am pretty far out of my depth with this...

@jklymak
Copy link
Member

jklymak commented Jul 14, 2023

Can this be reproduced just using log/log and contour labelling? Or is there something fancy about cartopy's projections that make things more complicated?

@rcomer
Copy link
Member Author

rcomer commented Jul 14, 2023

I do not know but I think it’s because Cartopy wraps the data around the world. For the test in question, it generates data with longitudes [0, 360] and plots a map with longitudes [-180, 180]. So some paths get broken (have more MOVETOs added) when they are transformed from data space to axes/screen space.

@greglucas
Copy link
Contributor

Or is there something fancy about cartopy's projections that make things more complicated?

Yes, Cartopy adds points to a line if the segment is long enough and wouldn't necessarily be straight. Think of some projections where you wouldn't want your line to be rendered straight, but rather you'd want it to be rendered as a curve (great circle arcs and the like). There are some thresholds you can set to how "smooth" you want that line approximation to be, and therefore how many points that get added can be variable.

For lines len(transform_path(path).vertices) may not equal len(path.vertices). But, for example on a scatter plot you just want to transform the points, but you don't care about any line smoothness, so that equality does hold then. len(transform(path).vertices) == len(path.vertices)

@jklymak
Copy link
Member

jklymak commented Jul 14, 2023

That makes some sense. However, ignorant of the details, I wonder if I'd not do that for contours. It seems that if a contour is so long that it's straight in lat/Lon space it's probably ok for it to be straight in the projection? Eg the data is very sparse and it doesn't matter?

@greglucas
Copy link
Contributor

Yeah, I think your comment is basically getting at where to do the contouring in data-space or in projected-data space. Contouring in Cartopy now has a "transform_first" option so that you can project all points and then do the contouring in projected space, rather than in data space, which does speed things up a lot (this is more akin to Basemap). https://scitools.org.uk/cartopy/docs/latest/gallery/scalar_data/contour_transforms.html#sphx-glr-gallery-scalar-data-contour-transforms-py

@jklymak
Copy link
Member

jklymak commented Jul 14, 2023

Cool. But I'm also saying that path complication doesn't seem necessary for contours. The line segments tend to be of similar size to the data resolution, and exactly whether the line is straight in data space or screen space shouldn't matter.

@rcomer rcomer deleted the clabel-transform-codes branch July 27, 2023 19:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants