Purpose
Longitude is a coordinate on a circle, not a line, and every operation here that treats it as a line — ring winding, bounding-box construction, buffer generation, planar intersection — breaks silently for any geometry crossing the ±180° meridian. This page states the exact detection test and exact mitigation so "handle the antimeridian" is never an unstated assumption.
The problem
A polygon with vertices at longitude 179.5° and −179.8° is 0.7° wide across the meridian, but a planar algorithm reading those numbers on a continuous line computes a span of 359.3° — effectively the entire globe minus a sliver. The same failure hits point-radius circles (a circle centered at 179.9° with a 50 km radius has an arc crossing 180°) and bounding boxes (a box whose "west" edge is numerically larger than its "east" edge, e.g. west = 170°, east = −170°, is a valid wrapping box, not an invalid one).
Source geometry and destination geometry
Source geometry is any polygon, point-radius circle, or bounding box
normalized to EPSG:4326 decimal degrees. Destination geometry is the same
geometry type, corrected, or an h3_cell_set if the corrected geometry is
then polyfilled. This page is a precondition for
geometry-normalization, not an alternative
to it — correction must run before self-intersection repair and before
polyfilling, since polyfilling an unsplit, meridian-crossing ring returns
either zero cells or every cell on the globe depending on which
winding-order edge case the library hits.
Exactness class
Exact. Correction is a coordinate-space transform, not an approximation: the corrected geometry represents the identical physical region as the source, with no coverage or area error introduced.
Detection
- Longitude span check
- For a ring, compute max(lng) − min(lng) using raw signed longitudes. A span greater than 180 degrees indicates the ring crosses the antimeridian (a correctly-behaved ring never legitimately spans more than 180 degrees in a single unwrapped pass).
- Sign change across consecutive vertices
- Walk the ring in order; if consecutive vertices flip sign (e.g. 179.6 then -179.9) AND the absolute difference exceeds 180 degrees, that edge crosses the meridian.
- Bounding box west greater than east
- A bounding box where west > east in raw signed degrees (e.g. west=170, east=-170) is a valid antimeridian-wrapping box, not a malformed one — reject any validator that treats west > east as an error.
- Circle center near ±180°
- For point-radius, flag any circle whose center longitude is within radius/111320 degrees (approximate meters-per-degree at the equator) of ±180°, since its geodesic buffer may cross regardless of the exact center value.
Mitigation
- Split at the antimeridian
- Cut the ring into two (or more) closed rings at longitude = 180 / -180, producing a valid multipolygon where each part has an unwrapped, non-crossing longitude range. This is the standard GeoJSON-correct representation.
- Great-circle densification before splitting
- Insert intermediate vertices along the true geodesic between the two vertices that straddle the meridian, so the split point is computed from the actual edge path rather than a straight planar interpolation, which is itself distorted near the pole-adjacent regions of a wide crossing.
- Longitude unwrapping for local operations
- For operations confined to a small window around the crossing (e.g. computing area or a local buffer), shift longitudes by +360 degrees wherever they are negative, so the whole geometry lies in a single continuous range (e.g. 179 to 181 instead of 179 to -179). Unwrap only for the scope of the local computation; never persist unwrapped coordinates as the canonical representation.
- h3-js isGeoJson handling
- h3-js's `polygonToCells` accepts a `GeoJsonPolygon` flag that changes ring-crossing behavior at the meridian; confirm which convention (raw signed degrees vs. pre-split multipolygon) the call site expects before passing a crossing ring directly, since passing an unsplit ring to a function expecting pre-split input silently returns the wrong hemisphere's cells.
Resolution behavior
Correction is resolution-independent — a coordinate transform applied once,
before polyfilling, whose correctness does not change with the H3
resolution chosen downstream. What does change with resolution is the
number of cells near the meridian whose own boundary crosses ±180°:
cellToBoundary output for those cells needs the same split/unwrap logic
before being rendered or intersected, since H3 cell boundaries are not
automatically corrected for meridian crossing by all consumers.
Units and CRS
EPSG:4326 decimal degrees throughout. Splitting and densification run in geodesic (great-circle) space, not a planar-projected CRS — projecting first would require a projection centered away from the crossing, which reintroduces the same problem at a different meridian.
Algorithm
import { normalizePolygon } from "@/lib/geometry/normalize";
import { polygonToH3 } from "@/lib/h3/polyfill";
// normalizePolygon detects span > 180deg and splits into a multipolygon
// with each part's longitudes in a continuous, non-wrapping range.
const normalized = normalizePolygon(rawPolygon);
// Polyfill each part independently; union the resulting cell sets.
const cells = normalized.parts.flatMap((part) =>
polygonToH3(part, { resolution: 8, mode: "intersect" }),
);
Parameters
None beyond the input geometry — a detection-and-correction pass, not a tunable conversion. The only implicit parameter is the densification sample count used to insert intermediate vertices before computing the split point.
Outputs
A GeoJSON multipolygon (or corrected bounding box / circle) with no ring spanning more than 180° of raw longitude, ready for downstream normalization and polyfilling.
Quality metrics
coverage_ratio and jaccard computed against the source, both of which
should equal 1.0 for a correct split (the correction is exact, so any
deviation indicates a bug in the split/densification, not an accepted
approximation).
Edge cases
Antimeridian is itself the edge case this page documents; it compounds with self-intersections when a source polygon both crosses the meridian and has invalid winding — splitting must run first, since repairing winding on an unsplit ring is undefined.
Assumptions and limitations
This page assumes the source geometry is otherwise valid GeoJSON — closed rings, no self-intersections away from the meridian. It does not address pole-adjacent densification distortion at very high latitudes near the crossing, best handled by increasing the sample count, not a different algorithm.
