Skip to content

Fix test failure with GEOS 3.15. - #746

Merged
djhoese merged 2 commits into
pytroll:mainfrom
sebastic:geos-3.15
Sep 9, 2026
Merged

djhoese merged 2 commits into
pytroll:mainfrom
sebastic:geos-3.15

Conversation

@sebastic

@sebastic sebastic commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

New Description

Pyresample fails to build with GEOS 3.15 due to test failures as reported in Debian Bug #1146573.

get_geostationary_bounding_box_in_proj_coords clips the geostationary disk against the area extent
with a shapely Polygon.intersection, and the vertex GEOS starts the resulting ring at changed in
3.15. Per the 3.15.0 release notes, under Breaking Changes:

Overlay operations that previously rotated rings by one vertex no longer do so (GH-1412, Dan Baston)

So GEOS <= 3.14 was the anomaly, not 3.15: the expected arrays in test_area_boundary.py were
captured against that spurious rotation, and on 3.15 the actual ring is np.roll(expected, 1).

Fixed by rotating both rings to a canonical starting vertex before comparing, so the tests pass on
either GEOS version. Vertex order, the pairing of the two coordinate arrays, winding direction and
the original tolerances all stay checked. A shapely.geos_version check would not work here — it
reports the version shapely was built against, not the runtime library.

Original Description

Pyresample fails to build with GEOS 3.15 due to test failures as reported in Debian Bug #1146573.

The starting vertex of polygon boundaries returned by GEOS intersection is not guaranteed and shifted in GEOS 3.15.

The starting vertex of polygon boundaries returned by GEOS intersection
is not guaranteed and shifted in GEOS 3.15.
@codecov

codecov Bot commented Sep 8, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 92.85714% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 93.68%. Comparing base (2e7a3c8) to head (8aca19c).
⚠️ Report is 15 commits behind head on main.

Files with missing lines Patch % Lines
...yresample/test/test_geometry/test_area_boundary.py 92.85% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #746      +/-   ##
==========================================
- Coverage   93.68%   93.68%   -0.01%     
==========================================
  Files          89       89              
  Lines       13715    13721       +6     
==========================================
+ Hits        12849    12854       +5     
- Misses        866      867       +1     
Flag Coverage Δ
unittests 93.68% <92.85%> (-0.01%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@djhoese djhoese left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Wow thanks for the fix. I had a couple questions about the new way of checking.

for k in range(len(desired)):
if np.allclose(actual, np.roll(desired, k, axis=0), **kwargs):
return
np.testing.assert_allclose(actual, desired, **kwargs)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What does this check do that the np.allclose check doesn't cover?


def _assert_ring_allclose(actual, desired, **kwargs):
for k in range(len(desired)):
if np.allclose(actual, np.roll(desired, k, axis=0), **kwargs):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Could this be np.testing.assert_allclose?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Note that allclose has more lenient tolerances (rtol=1e-5, atol=1e-08) versus assert_allclose (rtol=1e-7, atol=0) so these tests are much more accepting of incorrect values than they used to be.

Additionally, when we're comparing two sets of arrays (lons + lats or x + y) we're letting the k choice here go separately. That is, checking lons could have a k of 0 and lats could find a k of 2 (for example). Although unlikely to be a problem, this is another way in which the tests are less strict than before.

Lastly, I asked Claude AI to look at this and it pointed out that this way of doing this can also ignore the clockwise/counter-clockwise checks of the existing tests. That is, depending on the values, enough rolls can result in an array that is equal to the reversal of an array. This is less likely if the k is shared between the two coordinates, but the last check changed in this PR doesn't check the lats so there's still a chance there. We do have some test utilities for checking CW/CCW which should maybe be added. test/test_geometry/test_swath_boundary.py should have _is_clockwise(lons, lats).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Perhaps these tests should just be skipped with GEOS >= 3.15, unfortunately shapely.geos_version reports the build-time version of GEOS, not the runtime version.

@sebastic

sebastic commented Sep 8, 2026

Copy link
Copy Markdown
Contributor Author

Wow thanks for the fix. I had a couple questions about the new way of checking.

CCing @maxyz as author of these changes.

@djhoese

djhoese commented Sep 9, 2026

Copy link
Copy Markdown
Member

I was having trouble understanding how this would happen and the description that GEOS vertex order is no longer guaranteed. I asked Claude to take a look at the changelog and find the actual cause of all of this:

▎ - Breaking Changes:
▎   - Overlay operations that previously rotated rings by one vertex no longer do so (GH-1412, Dan Baston)

So it's not "vertex order is not guaranteed." It's "we were rotating rings by one vertex; we stopped." GEOS ≤ 3.14 was the anomaly.

The root cause, from the upstream JTS PR (locationtech/jts#1187, fixing JTS #865, ported as libgeos/geos#1412 "OverlayEdge: Avoid unintended ring rotation"):

▎ The isFirstEdge logic in OverlayEdge::addCoordinates appears to be backwards. It works out OK because it is only used to construct rings, and the dropped point ends up being added later on, with the ring being "rotated" by one vertex.
▎
▎ However, problems arise when extending this function to handle coordinates from CircularString because dropping the first point of the first arc causes a control point to be interpreted as an endpoint.

I'll work on a fix and update your branch. No need to close this.

@djhoese djhoese self-assigned this Sep 9, 2026
@djhoese djhoese added the bug label Sep 9, 2026
@djhoese

djhoese commented Sep 9, 2026

Copy link
Copy Markdown
Member

I added the changes Claude suggested and updated the description with Claude's shortest summary of the problem. This would have been much simpler of shapely provided access to the libgeos version number but 🤷‍♂️

Thanks @sebastic for forwarding the patch on.

@djhoese

djhoese commented Sep 9, 2026

Copy link
Copy Markdown
Member

The unstable CI env and the docs failures are not from this change. I think I'm going to merge this as the changes make sense to me and seem like an overall improvement. Thanks again @sebastic and @maxyz.

@djhoese
djhoese merged commit 57ff9b1 into pytroll:main Sep 9, 2026
24 of 37 checks passed
@sebastic
sebastic deleted the geos-3.15 branch September 9, 2026 18:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants