@@ -4,29 +4,30 @@ import "clip";
44import "circle" ;
55import "spherical" ;
66
7- // Clip features against a circle centered at [0°, 0°], with a given radius.
8- function d3_geo_clipCircle ( degrees ) {
9- var radians = degrees * d3_radians ,
10- cr = Math . cos ( radians ) ,
11- interpolate = d3_geo_circleInterpolate ( radians , 6 * d3_radians ) ;
7+ // Clip features against a small circle centered at [0°, 0°].
8+ function d3_geo_clipCircle ( radius ) {
9+ var cr = Math . cos ( radius ) ,
10+ smallRadius = cr > 0 ,
11+ notHemisphere = Math . abs ( cr ) > ε , // TODO optimise for this common case
12+ interpolate = d3_geo_circleInterpolate ( radius , 6 * d3_radians ) ;
1213
1314 return d3_geo_clip ( visible , clipLine , interpolate ) ;
1415
1516 function visible ( λ , φ ) {
1617 return Math . cos ( λ ) * Math . cos ( φ ) > cr ;
1718 }
1819
19- // TODO handle two invisible endpoints with visible intermediate segment.
2020 // Takes a line and cuts into visible segments. Return values used for
2121 // polygon clipping:
2222 // 0: there were intersections or the line was empty.
2323 // 1: no intersections.
2424 // 2: there were intersections, and the first and last segments should be
2525 // rejoined.
2626 function clipLine ( listener ) {
27- var point0 ,
28- v0 ,
29- v00 ,
27+ var point0 , // previous point
28+ c0 , // code for previous point
29+ v0 , // visibility of previous point
30+ v00 , // visibility of first point
3031 clean ; // no intersections
3132 return {
3233 lineStart : function ( ) {
@@ -36,9 +37,13 @@ function d3_geo_clipCircle(degrees) {
3637 point : function ( λ , φ ) {
3738 var point1 = [ λ , φ ] ,
3839 point2 ,
39- v = visible ( λ , φ ) ;
40+ v = visible ( λ , φ ) ,
41+ c = smallRadius
42+ ? v ? 0 : code ( λ , φ )
43+ : v ? code ( λ + ( λ < 0 ? π : - π ) , φ ) : 0 ;
4044 if ( ! point0 && ( v00 = v0 = v ) ) listener . lineStart ( ) ;
41- // handle degeneracies
45+ // Handle degeneracies.
46+ // TODO ignore if not clipping polygons.
4247 if ( v !== v0 ) {
4348 point2 = intersect ( point0 , point1 ) ;
4449 if ( d3_geo_sphericalEqual ( point0 , point2 ) || d3_geo_sphericalEqual ( point1 , point2 ) ) {
@@ -49,7 +54,7 @@ function d3_geo_clipCircle(degrees) {
4954 }
5055 if ( v !== v0 ) {
5156 clean = 0 ;
52- if ( v0 = v ) {
57+ if ( v ) {
5358 // outside going in
5459 listener . lineStart ( ) ;
5560 point2 = intersect ( point1 , point0 ) ;
@@ -61,9 +66,29 @@ function d3_geo_clipCircle(degrees) {
6166 listener . lineEnd ( ) ;
6267 }
6368 point0 = point2 ;
69+ } else if ( notHemisphere && point0 && smallRadius ^ v ) {
70+ var t ;
71+ // If the codes for two points are different, or are both zero,
72+ // and there this segment intersects with the small circle.
73+ if ( ! ( c & c0 ) && ( t = intersect ( point1 , point0 , true ) ) ) {
74+ clean = 0 ;
75+ if ( smallRadius ) {
76+ listener . lineStart ( ) ;
77+ listener . point ( t [ 0 ] [ 0 ] , t [ 0 ] [ 1 ] ) ;
78+ listener . point ( t [ 1 ] [ 0 ] , t [ 1 ] [ 1 ] ) ;
79+ listener . lineEnd ( ) ;
80+ } else {
81+ listener . point ( t [ 1 ] [ 0 ] , t [ 1 ] [ 1 ] ) ;
82+ listener . lineEnd ( ) ;
83+ listener . lineStart ( ) ;
84+ listener . point ( t [ 0 ] [ 0 ] , t [ 0 ] [ 1 ] ) ;
85+ }
86+ }
87+ }
88+ if ( v && ( ! point0 || ! d3_geo_sphericalEqual ( point0 , point1 ) ) ) {
89+ listener . point ( point1 [ 0 ] , point1 [ 1 ] ) ;
6490 }
65- if ( v && ( ! point0 || ! d3_geo_sphericalEqual ( point0 , point1 ) ) ) listener . point ( point1 [ 0 ] , point1 [ 1 ] ) ;
66- point0 = point1 ;
91+ point0 = point1 , v0 = v , c0 = c ;
6792 } ,
6893 lineEnd : function ( ) {
6994 if ( v0 ) listener . lineEnd ( ) ;
@@ -76,32 +101,76 @@ function d3_geo_clipCircle(degrees) {
76101 }
77102
78103 // Intersects the great circle between a and b with the clip circle.
79- function intersect ( a , b ) {
104+ function intersect ( a , b , two ) {
80105 var pa = d3_geo_cartesian ( a ) ,
81106 pb = d3_geo_cartesian ( b ) ;
107+
82108 // We have two planes, n1.p = d1 and n2.p = d2.
83- // Find intersection line p(t) = c1 n1 + c2 n2 + t (n1 x n2).
109+ // Find intersection line p(t) = c1 n1 + c2 n2 + t (n1 ⨯ n2).
84110 var n1 = [ 1 , 0 , 0 ] , // normal
85111 n2 = d3_geo_cartesianCross ( pa , pb ) ,
86112 n2n2 = d3_geo_cartesianDot ( n2 , n2 ) ,
87113 n1n2 = n2 [ 0 ] , // d3_geo_cartesianDot(n1, n2),
88114 determinant = n2n2 - n1n2 * n1n2 ;
115+
89116 // Two polar points.
90- if ( ! determinant ) return a ;
117+ if ( ! determinant ) return ! two && a ;
91118
92119 var c1 = cr * n2n2 / determinant ,
93120 c2 = - cr * n1n2 / determinant ,
94121 n1xn2 = d3_geo_cartesianCross ( n1 , n2 ) ,
95122 A = d3_geo_cartesianScale ( n1 , c1 ) ,
96123 B = d3_geo_cartesianScale ( n2 , c2 ) ;
97124 d3_geo_cartesianAdd ( A , B ) ;
98- // Now solve |p(t)|^2 = 1.
125+
126+ // Solve |p(t)|^2 = 1.
99127 var u = n1xn2 ,
100128 w = d3_geo_cartesianDot ( A , u ) ,
101129 uu = d3_geo_cartesianDot ( u , u ) ,
102- t = Math . sqrt ( w * w - uu * ( d3_geo_cartesianDot ( A , A ) - 1 ) ) ,
130+ t2 = w * w - uu * ( d3_geo_cartesianDot ( A , A ) - 1 ) ;
131+
132+ if ( t2 < 0 ) return ;
133+
134+ var t = Math . sqrt ( t2 ) ,
103135 q = d3_geo_cartesianScale ( u , ( - w - t ) / uu ) ;
104136 d3_geo_cartesianAdd ( q , A ) ;
105- return d3_geo_spherical ( q ) ;
137+ q = d3_geo_spherical ( q ) ;
138+ if ( ! two ) return q ;
139+
140+ // Two intersection points.
141+ var λ0 = a [ 0 ] ,
142+ λ1 = b [ 0 ] ,
143+ φ0 = a [ 1 ] ,
144+ φ1 = b [ 1 ] ,
145+ z ;
146+ if ( λ1 < λ0 ) z = λ0 , λ0 = λ1 , λ1 = z ;
147+ var δλ = λ1 - λ0 ,
148+ polar = Math . abs ( δλ - π ) < ε ,
149+ meridian = polar || δλ < ε ;
150+
151+ if ( ! polar && φ1 < φ0 ) z = φ0 , φ0 = φ1 , φ1 = z ;
152+
153+ // Check that the first point is between a and b.
154+ if ( meridian
155+ ? polar
156+ ? φ0 + φ1 > 0 ^ q [ 1 ] < ( Math . abs ( q [ 0 ] - λ0 ) < ε ? φ0 : φ1 )
157+ : φ0 <= q [ 1 ] && q [ 1 ] <= φ1
158+ : δλ > π ^ ( λ0 <= q [ 0 ] && q [ 0 ] <= λ1 ) ) {
159+ var q1 = d3_geo_cartesianScale ( u , ( - w + t ) / uu ) ;
160+ d3_geo_cartesianAdd ( q1 , A ) ;
161+ return [ q , d3_geo_spherical ( q1 ) ] ;
162+ }
163+ }
164+
165+ // Generates a 4-bit vector representing the location of a point relative to
166+ // the small circle's bounding box.
167+ function code ( λ , φ ) {
168+ var r = smallRadius ? radius : π - radius ,
169+ code = 0 ;
170+ if ( λ < - r ) code |= 1 ; // left
171+ else if ( λ > r ) code |= 2 ; // right
172+ if ( φ < - r ) code |= 4 ; // below
173+ else if ( φ > r ) code |= 8 ; // above
174+ return code ;
106175 }
107176}
0 commit comments