Skip to content

Take ChamberlainDuquetteArea's rings across the antimeridian the short way around - #1631

Closed
flight1973 wants to merge 8 commits into
georust:mainfrom
flight1973:fix/chamberlain-duquette-short-edges
Closed

flight1973 wants to merge 8 commits into
georust:mainfrom
flight1973:fix/chamberlain-duquette-short-edges

Conversation

@flight1973

@flight1973 flight1973 commented Nov 2, 2026 •

Copy link
Copy Markdown

ChamberlainDuquetteArea takes each edge's change in longitude as written, so a polygon that crosses the antimeridian without being cut there gets a wildly wrong area. For example:

polygon![(x: 179., y: 0.), (x: -179., y: 0.5), (x: -179., y: 1.5), (x: 179., y: 1.)]

measures -4,435,868 km² on main. The same polygon written with 181° instead of -179° measures 24,781 km². A ring around a pole that isn't closed along the pole, such as an uncut Antarctica coastline, comes out wrong too.

With this change, a ring that has an edge spanning more than 180° that isn't along a parallel takes its wide edges the short way around, as GeodesicArea does, and a ring that then winds around a pole gets the smaller side. Rings without such an edge are computed exactly as before, so boxes, Rects, latitude bands and polygons clipped along parallels are unchanged, bit for bit. On Natural Earth data the results are unchanged as published and when clipped; rotated so that polygons cross the antimeridian, those polygons now come out right, with no regressions.

This is a breaking change for rings with a wide edge that isn't along a parallel, including convex hulls or simplifications done in longitude and latitude that span more than 180°. The details are in the rustdoc and CHANGES.md.

Separately, holes wound opposite to their exterior are added instead of subtracted. That's an existing bug, not touched here; I'll open an issue for it.

AI disclosure: I used an LLM (Claude) to adapt work we had been doing for RustGIS to geo. It drafted the fix and the tests, which I reviewed.

  • I agree to follow the project's code of conduct.
  • I added an entry to CHANGES.md if knowledge of this change could be valuable to users.

Each edge's change in longitude was taken as written, so an edge from 179°
to -179° ran 358° the long way: a 1° square across the antimeridian measured
minus the area of the rest of its band of latitude (-4.4 million km²), and a
ring winding around a pole measured zero.

An edge written across more than 180° is now taken the short way, as
GeodesicArea takes it, by correcting its share of the sum, so a ring with no
such edge gives bit for bit what it did. A ring that then winds around a pole
gets 2πR² per turn and the smaller of the two sides it splits the Earth
into, negative when that side is on the ring's right (as GeodesicArea's
signed area is). An edge written from -180° to 180° now spans nothing.
An edge written across exactly 180° is meant to keep the direction it is
written in, but its span was compared in radians, where 180° can round
either side of π, so depending on its longitudes such an edge could be
taken the other way round. Spans are now compared in degrees, where an
edge across exactly 180° is exactly 180°.

A ring that splits the Earth into halves, such as one along the equator,
now gets half the Earth, positive, whichever way round it is written: the
result is in (-earth/2, earth/2].

The docs and CHANGES.md now say what a box wider than 180° of longitude
measures (the narrower box the other way round) and mark the change as
breaking.
@flight1973
flight1973 marked this pull request as draft November 2, 2026 23:55
A Rect spans from its minimum longitude east to its maximum, however
wide, but it was measured as a polygon, whose edges are now each taken
the short way around: the whole world measured nothing, and a box from
-100° to 100° measured the narrower box the other way round. A Rect is
now measured directly, as it was before.

A ring with an edge written across more than 180° of longitude was summed
as written and then corrected by whole turns. The correction cancelled
terms of the order of 2π, which cost precision for small polygons across
the antimeridian (an f32 box 0.01° wide measured nothing) and gave
believable but wrong areas, or NaN, for longitudes written far out of
range. Such a ring is now summed edge by edge, with each change in
longitude reduced exactly and the winding counted from the reduced
changes. A ring with no such edge takes the same sum as before, bit for
bit, in a single pass.

An edge written across 540° (or 180° plus any whole number of turns) now
keeps the direction it is written in, as one across 180° does.
… written

An edge from -180° to 180° along a parallel runs all the way round it, as
in a box spanning the world or a polygon clipped along a parallel, and an
edge along a pole goes nowhere whatever its change in longitude. Taking
either the short way made a world box measure nothing, a polygon clipped
to Web Mercator's limit gain the cap beyond it, and a ring closed along a
pole wind around the other pole and measure its smaller side. Both are
taken as written again, and a ring whose only edges wider than 180° are
such edges takes the same sum as before, bit for bit.

An edge with an end more than a million degrees out isn't a longitude, so
its ring is taken as written, as before, rather than reduced to a
plausible area. The reduction no longer uses `%`, whose cost grows with
the size of its operands, and the turns taken off each edge are counted as
an integer, so a ring's winding no longer depends on rounding.

The sum for a ring with an edge taken the short way takes its latitudes'
sines relative to the first vertex's, so a small polygon across the
antimeridian keeps its precision in f32 as well as f64, whatever its
shape.
Chamberlain and Duquette's sum treats an edge as a straight line in
longitude and the sine of latitude, along which an edge along a parallel
is exact: a box is as wide as it is written, and the pieces of a polygon
clipped along parallels add up to it. Clippers write such edges wider
than 180°, and taking them the short way made a strip from 57°W to 140°E,
as a clip leaves it, measure some fifty times its area, and Antarctica
clipped to Web Mercator's limit measure more than Antarctica. An edge
along a parallel (to within a few units in the last place) is taken as
written again, which covers those along a pole and from -180° to 180°,
and a `Rect` is measured as its polygon again. So a box across the
antimeridian written with only its corners is as wide as it is written,
as it always was.

A polygon with a ring winding around a pole is reduced to the smaller
side once, after its holes are subtracted, rather than ring by ring, so a
band between two rings around a pole measures the band. A ring with any
longitude more than a million degrees out is taken as written throughout.
The change in longitude of an edge taken the short way is taken from its
ends rather than from the change as written, which has rounded to the
precision of a value near a whole turn.
…ritten

Deciding edge by edge, a ring that crossed the antimeridian once along a
parallel and once off one took the first the long way and the second the
short way, so it wound around a pole and measured about a polar cap: an
island whose coordinates are rounded, say, or a polygon clipped along a
parallel across the antimeridian. Now a ring with an edge wider than 180°
off a parallel takes each of its edges wider than 180° the short way,
except along a pole, and any other ring is taken as written. An edge is
along a parallel to within a few units in the last place of its latitude
rather than of 360°, which in f32 was several meters, and along a pole to
within 1e-9°.

A polygon whose exterior doesn't wind around a pole now cuts the smaller
side out of each of its holes that does, rather than reducing the whole
polygon to its smaller side, so the world less Antarctica measures the
world less Antarctica. A total too far out for its rings' winding to
explain, as from a ring taken as written, is left as it is rather than
reduced to a plausible area. A longitude beyond ±720°, rather than a
million degrees, is too far out to be one.
… with its sign

An edge ran along a parallel to within a few units in the last place of
its latitude, which is nothing at the equator: a band from -180° to 180°
with a corner 1e-17° below it, or with one corner through a round trip to
Web Mercator, was taken the short way and measured nothing. The tolerance
is now at least 1e-9°, and a change in longitude is 180°, or more than
180°, by the same measure, so a change of 180° that computes to
180.00000000000003, as in data written from 0° to 360°, keeps its
direction.

A ring's winding is now counted with its sign, so a polygon whose holes
wind it back, such as a band between two rings around a pole, measures
what it encloses rather than its smaller side. And each ring's area is
checked against its own winding, so a hole taken as written is no longer
reduced to a plausible area by an exterior that winds many times.
In f32, 180° plus 1e-9° is 180°, so an edge written across 180° whose
change in longitude computes to a unit in the last place more, as about
one in ten do in data written from 0° to 360°, was taken the other way
round; and a tolerance of a few units in the last place of the latitude
was next to nothing near the equator, so a band with a corner through a
round trip to Web Mercator measured nothing. An edge now runs along a
parallel, or a pole, to within 1e-9° or the type's precision near 360°
if that is coarser, and a change in longitude is 180° to within that or
the precision of the longitudes. In f64 neither is more than 1e-9°, so
nothing changes there.

A hole winding around a pole in an exterior that doesn't now cuts out
the smaller of its sides whichever way it winds, rather than adding it
when it lies on the hole's right.
@flight1973 flight1973 changed the title Take ChamberlainDuquetteArea's edges the short way around in longitude Take ChamberlainDuquetteArea's rings across the antimeridian the short way around Nov 3, 2026
@flight1973
flight1973 marked this pull request as ready for review November 3, 2026 02:44
@michaelkirk

Copy link
Copy Markdown
Member

Please re-open with a description written for human consumption.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants