@@ -79,6 +79,28 @@ function locationToFeature(locationmode, location, features) {
7979 return false ;
8080}
8181
82+ // Offset used to lift negative longitudes (-180..0) into a continuous frame
83+ // (180..360) so polygons and points that straddle the antimeridian can be
84+ // compared with linear math. Shared between polygon stitching and hover
85+ // hit-testing so both sides stay in sync.
86+ const ANTIMERIDIAN_LON_SHIFT = 360 ;
87+
88+ /**
89+ * Find the first index where a polygon ring crosses the antimeridian
90+ * (a transition from positive to negative longitude between consecutive
91+ * points). Returns null when no crossing is found.
92+ *
93+ * @param {Array<Array<number>> } pts - polygon points as [lon, lat] pairs
94+ * @return {number|null } index of the segment that crosses, or null
95+ */
96+ function doesCrossAntiMeridian ( pts ) {
97+ for ( let l = 0 ; l < pts . length - 1 ; l ++ ) {
98+ if ( pts [ l ] [ 0 ] > 0 && pts [ l + 1 ] [ 0 ] < 0 ) return l ;
99+ }
100+
101+ return null ;
102+ }
103+
82104function feature2polygons ( feature ) {
83105 var geometry = feature . geometry ;
84106 var coords = geometry . coordinates ;
@@ -87,13 +109,6 @@ function feature2polygons(feature) {
87109 var polygons = [ ] ;
88110 var appendPolygon , j , k , m ;
89111
90- function doesCrossAntiMerdian ( pts ) {
91- for ( var l = 0 ; l < pts . length - 1 ; l ++ ) {
92- if ( pts [ l ] [ 0 ] > 0 && pts [ l + 1 ] [ 0 ] < 0 ) return l ;
93- }
94- return null ;
95- }
96-
97112 if ( loc === 'RUS' || loc === 'FJI' ) {
98113 // Russia and Fiji have landmasses that cross the antimeridian,
99114 // we need to add +360 to their longitude coordinates, so that
@@ -105,13 +120,13 @@ function feature2polygons(feature) {
105120 appendPolygon = function ( _pts ) {
106121 var pts ;
107122
108- if ( doesCrossAntiMerdian ( _pts ) === null ) {
123+ if ( doesCrossAntiMeridian ( _pts ) === null ) {
109124 pts = _pts ;
110125 } else {
111126 pts = new Array ( _pts . length ) ;
112127 for ( m = 0 ; m < _pts . length ; m ++ ) {
113128 // do not mutate calcdata[i][j].geojson !!
114- pts [ m ] = [ _pts [ m ] [ 0 ] < 0 ? _pts [ m ] [ 0 ] + 360 : _pts [ m ] [ 0 ] , _pts [ m ] [ 1 ] ] ;
129+ pts [ m ] = [ _pts [ m ] [ 0 ] < 0 ? _pts [ m ] [ 0 ] + ANTIMERIDIAN_LON_SHIFT : _pts [ m ] [ 0 ] , _pts [ m ] [ 1 ] ] ;
115130 }
116131 }
117132
@@ -121,7 +136,7 @@ function feature2polygons(feature) {
121136 // Antarctica has a landmass that wraps around every longitudes which
122137 // confuses the 'contains' methods.
123138 appendPolygon = function ( pts ) {
124- var crossAntiMeridianIndex = doesCrossAntiMerdian ( pts ) ;
139+ var crossAntiMeridianIndex = doesCrossAntiMeridian ( pts ) ;
125140
126141 // polygon that do not cross anti-meridian need no special handling
127142 if ( crossAntiMeridianIndex === null ) {
@@ -139,7 +154,7 @@ function feature2polygons(feature) {
139154
140155 for ( m = 0 ; m < pts . length ; m ++ ) {
141156 if ( m > crossAntiMeridianIndex ) {
142- stitch [ si ++ ] = [ pts [ m ] [ 0 ] + 360 , pts [ m ] [ 1 ] ] ;
157+ stitch [ si ++ ] = [ pts [ m ] [ 0 ] + ANTIMERIDIAN_LON_SHIFT , pts [ m ] [ 1 ] ] ;
143158 } else if ( m === crossAntiMeridianIndex ) {
144159 stitch [ si ++ ] = pts [ m ] ;
145160 stitch [ si ++ ] = [ pts [ m ] [ 0 ] , - 90 ] ;
@@ -426,7 +441,20 @@ function getFitboundsLonRange(lons) {
426441 const antimeridianGap = 360 - naiveSpan ;
427442 if ( maxGap <= antimeridianGap ) return null ;
428443
429- return [ sorted [ gapStart + 1 ] , sorted [ gapStart ] + 360 ] ;
444+ return [ sorted [ gapStart + 1 ] , sorted [ gapStart ] + ANTIMERIDIAN_LON_SHIFT ] ;
445+ }
446+
447+ /**
448+ * Return a monotonic version of a `[lon0, lon1]` longitude range so its
449+ * midpoint and span can be computed as if longitude were a regular linear
450+ * coordinate. When the range crosses the antimeridian (`lon0 > 0`, `lon1 < 0`)
451+ * `lon1` is shifted by +360°; otherwise the input pair is returned unchanged.
452+ *
453+ * @param {[number, number] } lonRange - `[lon0, lon1]`, each in [-180, 180]
454+ * @return {[number, number] } the unwrapped range
455+ */
456+ function unwrapLonRange ( [ lon0 , lon1 ] ) {
457+ return [ lon0 , lon0 > 0 && lon1 < 0 ? lon1 + ANTIMERIDIAN_LON_SHIFT : lon1 ] ;
430458}
431459
432460module . exports = {
@@ -436,5 +464,8 @@ module.exports = {
436464 extractTraceFeature,
437465 fetchTraceGeoData,
438466 computeBbox,
439- getFitboundsLonRange
467+ doesCrossAntiMeridian,
468+ getFitboundsLonRange,
469+ unwrapLonRange,
470+ ANTIMERIDIAN_LON_SHIFT
440471} ;
0 commit comments