Skip to content

FIX: line segments that project to same start and end point retain length - #2749

Open
rcomer wants to merge 1 commit into
SciTools:mainfrom
rcomer:project-lines
Open

rcomer wants to merge 1 commit into
SciTools:mainfrom
rcomer:project-lines

Conversation

@rcomer

@rcomer rcomer commented Sep 24, 2026 •

Copy link
Copy Markdown
Member

Rationale

Fixes #2745

When we project a line segment to a different CRS, we use the bisect function to add extra points between the input ones, in order to produce a suitable curve if necessary. bisect uses straightAndDomain to decide whether the straight line between the given two points it is looking at are close enough to the actual curve. If they are it says "I'm done", if not it continues to add more points.

Currently, if the projected points are the same then along will be nan and so straightAndDomain returns True and bisect says "I'm done". So we end up with just the two points on top of each other. This PR adds an extra check so that bisect continues if the projected points are the same and the projection of their midpoint is different, as is the case when the line is wrapped around the equator.

I'm not sure about the exact form of the check: is there any way that along will be nan without seg_hypot_sq being zero?

Implications

@greglucas

Copy link
Copy Markdown
Contributor

I thought that we had something in the code that said we take the shorter path around. If we have a line defined as going from (-170, 0) to (170, 0) how do I know I want that line going through (0, 0) instead of through (+/-180, 0).

So in this case, this would be a single point on the line.

@rcomer

rcomer commented Sep 25, 2026

Copy link
Copy Markdown
Member Author

Given

import cartopy.crs as ccrs
import matplotlib.pyplot as plt
import shapely


tgt_proj = ccrs.NorthPolarStereo(central_longitude=180)
src_proj = ccrs.PlateCarree(central_longitude=180)
start = [-170, 0]
end = [170, 0]

line_string = shapely.LineString([start, end])

fig, ax = plt.subplots(subplot_kw={'projection':tgt_proj})
ax.add_geometries(line_string, src_proj, facecolor='none', edgecolor='blue')

plt.show()

With both 0.25 and 0.26 I get
image

@greglucas

Copy link
Copy Markdown
Contributor

I'm still a bit confused for when we decide to use "wrappable" coordinates versus not in various functions (like quadmesh and other draws).

Changing your example to: src_proj = ccrs.PlateCarree(central_longitude=180).as_geodetic() takes the shorter path. So we take the short path when it is a geodetic CRS, but I guess it does make sense that if I have a plane I want my lines going straight in that source plane and not switching backwards arbitrarily. If I want to cross the boundary, then I would need to extend beyond the -180/180 bounds. (Changing the example to start = [190, 0] also takes the short path)

@rcomer

rcomer commented Sep 25, 2026

Copy link
Copy Markdown
Member Author

I fell down a rabbit-hole looking at the history of the Iris code involved in #2747. I found this explanation, which is basically what you just said #368 (comment).

@QuLogic

QuLogic commented Sep 25, 2026

Copy link
Copy Markdown
Member

Are we not still taking the shortest path? PlateCarree is not spherical; it is a projection with a real break in it.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

3 participants