@@ -19,6 +19,7 @@ import (
19
19
"testing"
20
20
21
21
"github.com/golang/geo/r1"
22
+ "github.com/golang/geo/r2"
22
23
"github.com/golang/geo/r3"
23
24
"github.com/golang/geo/s1"
24
25
)
@@ -1129,3 +1130,79 @@ func TestRectApproxEqual(t *testing.T) {
1129
1130
}
1130
1131
}
1131
1132
}
1133
+
1134
+ func TestRectCentroidEmptyFull (t * testing.T ) {
1135
+ // Empty and full rectangles.
1136
+ if got , want := EmptyRect ().Centroid (), (Point {}); ! got .ApproxEqual (want ) {
1137
+ t .Errorf ("%v.Centroid() = %v, want %v" , EmptyRect (), got , want )
1138
+ }
1139
+ if got , want := FullRect ().Centroid ().Norm (), epsilon ; ! float64Eq (got , want ) {
1140
+ t .Errorf ("%v.Centroid().Norm() = %v, want %v" , FullRect (), got , want )
1141
+ }
1142
+ }
1143
+
1144
+ func testRectCentroidSplitting (t * testing.T , r Rect , leftSplits int ) {
1145
+ // Recursively verify that when a rectangle is split into two pieces, the
1146
+ // centroids of the children sum to give the centroid of the parent.
1147
+ var child0 , child1 Rect
1148
+ if oneIn (2 ) {
1149
+ lat := randomUniformFloat64 (r .Lat .Lo , r .Lat .Hi )
1150
+ child0 = Rect {r1.Interval {r .Lat .Lo , lat }, r .Lng }
1151
+ child1 = Rect {r1.Interval {lat , r .Lat .Hi }, r .Lng }
1152
+ } else {
1153
+ lng := randomUniformFloat64 (r .Lng .Lo , r .Lng .Hi )
1154
+ child0 = Rect {r .Lat , s1.Interval {r .Lng .Lo , lng }}
1155
+ child1 = Rect {r .Lat , s1.Interval {lng , r .Lng .Hi }}
1156
+ }
1157
+
1158
+ if got , want := r .Centroid ().Sub (child0 .Centroid ().Vector ).Sub (child1 .Centroid ().Vector ).Norm (), 1e-15 ; got > want {
1159
+ t .Errorf ("%v.Centroid() - %v.Centroid() - %v.Centroid = %v, want ~0" , r , child0 , child1 , got )
1160
+ }
1161
+ if leftSplits > 0 {
1162
+ testRectCentroidSplitting (t , child0 , leftSplits - 1 )
1163
+ testRectCentroidSplitting (t , child1 , leftSplits - 1 )
1164
+ }
1165
+ }
1166
+
1167
+ func TestRectCentroidFullRange (t * testing.T ) {
1168
+ // Rectangles that cover the full longitude range.
1169
+ for i := 0 ; i < 100 ; i ++ {
1170
+ lat1 := randomUniformFloat64 (- math .Pi / 2 , math .Pi / 2 )
1171
+ lat2 := randomUniformFloat64 (- math .Pi / 2 , math .Pi / 2 )
1172
+ r := Rect {r1.Interval {lat1 , lat2 }, s1 .FullInterval ()}
1173
+ centroid := r .Centroid ()
1174
+ if want := 0.5 * (math .Sin (lat1 ) + math .Sin (lat2 )) * r .Area (); ! float64Near (want , centroid .Z , epsilon ) {
1175
+ t .Errorf ("%v.Centroid().Z was %v, want %v" , r , centroid .Z , want )
1176
+ }
1177
+ if got := (r2.Point {centroid .X , centroid .Y }.Norm ()); got > epsilon {
1178
+ t .Errorf ("%v.Centroid().Norm() was %v, want > %v " , r , got , epsilon )
1179
+ }
1180
+ }
1181
+
1182
+ // Rectangles that cover the full latitude range.
1183
+ for i := 0 ; i < 100 ; i ++ {
1184
+ lat1 := randomUniformFloat64 (- math .Pi , math .Pi )
1185
+ lat2 := randomUniformFloat64 (- math .Pi , math .Pi )
1186
+ r := Rect {r1.Interval {- math .Pi / 2 , math .Pi / 2 }, s1.Interval {lat1 , lat2 }}
1187
+ centroid := r .Centroid ()
1188
+
1189
+ if got , want := math .Abs (centroid .Z ), epsilon ; got > want {
1190
+ t .Errorf ("math.Abs(%v.Centroid().Z) = %v, want <= %v" , r , got , want )
1191
+ }
1192
+
1193
+ if got , want := LatLngFromPoint (centroid ).Lng .Radians (), r .Lng .Center (); ! float64Near (got , want , epsilon ) {
1194
+ t .Errorf ("%v.Lng.Radians() = %v, want %v" , centroid , got , want )
1195
+ }
1196
+
1197
+ alpha := 0.5 * r .Lng .Length ()
1198
+ if got , want := (r2.Point {centroid .X , centroid .Y }.Norm ()), (0.25 * math .Pi * math .Sin (alpha ) / alpha * r .Area ()); ! float64Near (got , want , epsilon ) {
1199
+ t .Errorf ("%v.Centroid().Norm() = %v, want ~%v" , got , want , epsilon )
1200
+ }
1201
+ }
1202
+
1203
+ // Finally, verify that when a rectangle is recursively split into pieces,
1204
+ // the centroids of the pieces add to give the centroid of their parent.
1205
+ // To make the code simpler we avoid rectangles that cross the 180 degree
1206
+ // line of longitude.
1207
+ testRectCentroidSplitting (t , Rect {r1.Interval {- math .Pi / 2 , math .Pi / 2 }, s1.Interval {- math .Pi , math .Pi }}, 10 )
1208
+ }
0 commit comments