Fix contains() for polygon edges spanning exactly 180° of longitude#29
Open
gistrec wants to merge 1 commit into
Open
Fix contains() for polygon edges spanning exactly 180° of longitude#29gistrec wants to merge 1 commit into
gistrec wants to merge 1 commit into
Conversation
Port the missing upstream guard (PolyUtil: `if (lng2 <= -PI) return false`) in detail::intersects(). Without it, an edge whose endpoints are exactly 180 degrees of longitude apart (wrap yields -pi) fell through to tan_lat_gc, dividing by sin(-pi) ~ -1.2e-16 and flipping the ray-casting parity arbitrarily; rhumb mode silently diverged from upstream.
This was referenced Jun 10, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Bug
detail::intersects()dropped a guard that upstream PolyUtil has:contains()normalizes every edge so its first endpoint is at longitude 0 and the second atlng2 = wrap(lng2 - lng1, -π, π). When the endpoints are exactly 180° of longitude apart (e.g.0 → 180,-90 → 90,20 → -160— any pair of "clean" coordinatesXandX ± 180),wrapyields exactly-π. Such an edge is ambiguous — two equal great-circle arcs connect its endpoints — and upstream's convention is to never count it as an intersection of the South-Pole test ray.Without the guard the port fell through:
tan_lat_gcdivides bysin(-π) ≈ -1.2e-16, producing ±1.5e15 garbage whose comparison flips the ray-casting parity arbitrarily;mercator_lat_rhumbreturns a deterministic value for a direction upstream never evaluates — silent divergence.Impact
Sweep of 35×72 query points against polygons containing one 180°-spanning edge, port vs upstream semantics:
{(10,0), (-20,180), (40,90)}{(0,-90), (30,90), (60,0)}{(-10,20), (15,-160), (5,-60)}Minimal repro:
contains({0,-90}, {{10,0},{-20,180},{40,90}})returnedtrue; upstream (and now this port) returnsfalse.Fix
One early return in
detail::intersects()after the pole checks, mirroring upstream. Behavior on polygons without 180°-spanning edges is unchanged — verified bit-identical on 3000 benchmark queries (regular polygons of 10/100/1000 vertices).Performance
Paired benchmarks (10 interleaved base/fixed runs, median of per-pair deltas, Apple M1):
BM_GeoUtils_Contains/10(repo harness)BM_GeoUtils_Contains/100BM_GeoUtils_Contains/1000The real arithmetic cost is one well-predicted compare per candidate edge (sub-ns); the swings in both directions are compiler inlining/code-layout effects, not algorithmic changes (results are bit-identical). Polygons that do contain 180° edges get strictly faster (early out instead of trig).
Tests
New
TEST(Poly, contains_180deg_edge): the regression triangle above (both modes) plus a degenerate two-point "hemisphere"{(0,0),(0,180)}where every edge spans 180°. Suite: 35/35 green.Note
Behavior change for affected inputs — worth bundling into the next minor release together with #24–#27.