1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package s2
16
17 import (
18 "math"
19 "reflect"
20 "testing"
21
22 "github.com/golang/geo/r3"
23 "github.com/golang/geo/s1"
24 )
25
26 func TestPolylineBasics(t *testing.T) {
27 empty := Polyline{}
28 if empty.RectBound() != EmptyRect() {
29 t.Errorf("empty.RectBound() = %v, want %v", empty.RectBound(), EmptyRect())
30 }
31 if len(empty) != 0 {
32 t.Errorf("empty Polyline should have no vertices")
33 }
34 empty.Reverse()
35 if len(empty) != 0 {
36 t.Errorf("reveresed empty Polyline should have no vertices")
37 }
38
39 latlngs := []LatLng{
40 LatLngFromDegrees(0, 0),
41 LatLngFromDegrees(0, 90),
42 LatLngFromDegrees(0, 180),
43 }
44
45 semiEquator := PolylineFromLatLngs(latlngs)
46 want := PointFromCoords(0, 1, 0)
47 if got, _ := semiEquator.Interpolate(0.5); !got.ApproxEqual(want) {
48 t.Errorf("semiEquator.Interpolate(0.5) = %v, want %v", got, want)
49 }
50 semiEquator.Reverse()
51 if got, want := (*semiEquator)[2], (Point{r3.Vector{1, 0, 0}}); !got.ApproxEqual(want) {
52 t.Errorf("semiEquator[2] = %v, want %v", got, want)
53 }
54 }
55
56 func TestPolylineShape(t *testing.T) {
57 var shape Shape = makePolyline("0:0, 1:0, 1:1, 2:1")
58 if got, want := shape.NumEdges(), 3; got != want {
59 t.Errorf("%v.NumEdges() = %v, want %d", shape, got, want)
60 }
61 if got, want := shape.NumChains(), 1; got != want {
62 t.Errorf("%v.NumChains() = %d, want %d", shape, got, want)
63 }
64 if got, want := shape.Chain(0).Start, 0; got != want {
65 t.Errorf("%v.Chain(0).Start = %d, want %d", shape, got, want)
66 }
67 if got, want := shape.Chain(0).Length, 3; got != want {
68 t.Errorf("%v.Chain(0).Length = %d, want %d", shape, got, want)
69 }
70
71 e := shape.Edge(2)
72 if want := PointFromLatLng(LatLngFromDegrees(1, 1)); !e.V0.ApproxEqual(want) {
73 t.Errorf("%v.Edge(%d) point A = %v want %v", shape, 2, e.V0, want)
74 }
75 if want := PointFromLatLng(LatLngFromDegrees(2, 1)); !e.V1.ApproxEqual(want) {
76 t.Errorf("%v.Edge(%d) point B = %v want %v", shape, 2, e.V1, want)
77 }
78 if shape.ReferencePoint().Contained {
79 t.Errorf("polylines should not contain their reference points")
80 }
81 if got, want := shape.Dimension(), 1; got != want {
82 t.Errorf("shape.Dimension() = %v, want %v", got, want)
83 }
84 }
85
86 func TestPolylineEmpty(t *testing.T) {
87 shape := &Polyline{}
88 if got, want := shape.NumEdges(), 0; got != want {
89 t.Errorf("%v.NumEdges() = %d, want %d", shape, got, want)
90 }
91 if got, want := shape.NumChains(), 0; got != want {
92 t.Errorf("%v.NumChains() = %d, want %d", shape, got, want)
93 }
94 if !shape.IsEmpty() {
95 t.Errorf("shape.IsEmpty() = false, want true")
96 }
97 if shape.IsFull() {
98 t.Errorf("shape.IsFull() = true, want false")
99 }
100 if shape.ReferencePoint().Contained {
101 t.Errorf("polylines should not contain their reference points")
102 }
103 }
104
105 func TestPolylineLengthAndCentroid(t *testing.T) {
106
107
108
109
110
111 for i := 0; i < 100; i++ {
112
113 f := randomFrame()
114
115 var line Polyline
116 for theta := 0.0; theta < 2*math.Pi; theta += math.Pow(randomFloat64(), 10) {
117 p := Point{f.row(0).Mul(math.Cos(theta)).Add(f.row(1).Mul(math.Sin(theta)))}
118 if len(line) == 0 || !p.ApproxEqual(line[len(line)-1]) {
119 line = append(line, p)
120 }
121 }
122
123
124 line = append(line, line[0])
125
126 length := line.Length()
127 if got, want := math.Abs(length.Radians()-2*math.Pi), 2e-14; got > want {
128 t.Errorf("%v.Length() = %v, want < %v", line, got, want)
129 }
130
131 centroid := line.Centroid()
132 if got, want := centroid.Norm(), 2e-14; got > want {
133 t.Errorf("%v.Norm() = %v, want < %v", centroid, got, want)
134 }
135 }
136 }
137
138 func TestPolylineIntersectsCell(t *testing.T) {
139 pline := Polyline{
140 Point{r3.Vector{1, -1.1, 0.8}.Normalize()},
141 Point{r3.Vector{1, -0.8, 1.1}.Normalize()},
142 }
143
144 for face := 0; face < 6; face++ {
145 cell := CellFromCellID(CellIDFromFace(face))
146 if got, want := pline.IntersectsCell(cell), face&1 == 0; got != want {
147 t.Errorf("%v.IntersectsCell(%v) = %v, want %v", pline, cell, got, want)
148 }
149 }
150 }
151
152 func TestPolylineSubsample(t *testing.T) {
153 const polyStr = "0:0, 0:1, -1:2, 0:3, 0:4, 1:4, 2:4.5, 3:4, 3.5:4, 4:4"
154
155 tests := []struct {
156 have string
157 tolerance float64
158 want []int
159 }{
160 {
161
162 tolerance: 1.0,
163 },
164 {
165
166 have: "0:1",
167 tolerance: 1.0,
168 want: []int{0},
169 },
170 {
171
172 have: "10:10, 11:11",
173 tolerance: 5.0,
174 want: []int{0, 1},
175 },
176 {
177
178
179 have: "-1:0, 0:0, 1:0",
180 tolerance: 1e-15,
181 want: []int{0, 2},
182 },
183 {
184
185 have: "-1:0, 0:0, 1:1",
186 tolerance: 0.0,
187 want: []int{0, 1, 2},
188 },
189 {
190
191 have: "-1:0, 0:0, 1:1",
192 tolerance: -1.0,
193 want: []int{0, 1, 2},
194 },
195 {
196
197 have: "0:1, 0:2, 0:3, 0:4, 0:5",
198 tolerance: 1.0,
199 want: []int{0, 4},
200 },
201 {
202
203
204
205 have: "0:1, 0:1, 0:1, 0:2",
206 tolerance: 0.0,
207 want: []int{0, 3},
208 },
209
210
211 {
212 have: polyStr,
213 tolerance: 3.0,
214 want: []int{0, 9},
215 },
216 {
217 have: polyStr,
218 tolerance: 2.0,
219 want: []int{0, 6, 9},
220 },
221 {
222 have: polyStr,
223 tolerance: 0.9,
224 want: []int{0, 2, 6, 9},
225 },
226 {
227 have: polyStr,
228 tolerance: 0.4,
229 want: []int{0, 1, 2, 3, 4, 6, 9},
230 },
231 {
232 have: polyStr,
233 tolerance: 0,
234 want: []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
235 },
236
237
238 {
239 have: "10:10, 12:12, 10:10",
240 tolerance: 5.0,
241 want: []int{0},
242 },
243 {
244 have: "0:0, 1:1, 0:0, 0:120, 0:130",
245 tolerance: 5.0,
246 want: []int{0, 3, 4},
247 },
248
249
250
251
252 {
253 have: "90:0, 50:180, 20:180, -20:180, -50:180, -90:0, 30:0, 90:0",
254 tolerance: 5.0,
255 want: []int{0, 2, 4, 5, 6, 7},
256 },
257
258
259
260
261
262
263 {
264 have: "10:10, 10:20, 10:30, 10:15, 10:40",
265 tolerance: 5.0,
266 want: []int{0, 2, 3, 4},
267 },
268 {
269 have: "10:10, 10:20, 10:30, 10:10, 10:30, 10:40",
270 tolerance: 5.0,
271 want: []int{0, 2, 3, 5},
272 },
273 {
274 have: "10:10, 12:12, 9:9, 10:20, 10:30",
275 tolerance: 5.0,
276 want: []int{0, 4},
277 },
278 }
279
280 for _, test := range tests {
281 p := makePolyline(test.have)
282 got := p.SubsampleVertices(s1.Angle(test.tolerance) * s1.Degree)
283 if !reflect.DeepEqual(got, test.want) {
284 t.Errorf("%q.SubsampleVertices(%v°) = %v, want %v", test.have, test.tolerance, got, test.want)
285 }
286 }
287 }
288
289 func TestProject(t *testing.T) {
290 latlngs := []LatLng{
291 LatLngFromDegrees(0, 0),
292 LatLngFromDegrees(0, 1),
293 LatLngFromDegrees(0, 2),
294 LatLngFromDegrees(1, 2),
295 }
296 line := PolylineFromLatLngs(latlngs)
297 tests := []struct {
298 haveLatLng LatLng
299 wantProjection LatLng
300 wantNext int
301 }{
302 {LatLngFromDegrees(0.5, -0.5), LatLngFromDegrees(0, 0), 1},
303 {LatLngFromDegrees(0.5, 0.5), LatLngFromDegrees(0, 0.5), 1},
304 {LatLngFromDegrees(0.5, 1), LatLngFromDegrees(0, 1), 2},
305 {LatLngFromDegrees(-0.5, 2.5), LatLngFromDegrees(0, 2), 3},
306 {LatLngFromDegrees(2, 2), LatLngFromDegrees(1, 2), 4},
307 {LatLngFromDegrees(-50, 0.5), LatLngFromDegrees(0, 0.5), 1},
308 }
309 for _, test := range tests {
310 projection, next := line.Project(PointFromLatLng(test.haveLatLng))
311 if !PointFromLatLng(test.wantProjection).ApproxEqual(projection) {
312 t.Errorf("line.Project(%v) = %v, want %v", test.haveLatLng, projection, test.wantProjection)
313 }
314 if next != test.wantNext {
315 t.Errorf("line.Project(%v) = %v, want %v", test.haveLatLng, next, test.wantNext)
316 }
317 }
318 }
319
320 func TestIsOnRight(t *testing.T) {
321 latlngs := []LatLng{
322 LatLngFromDegrees(0, 0),
323 LatLngFromDegrees(0, 1),
324 LatLngFromDegrees(0, 2),
325 LatLngFromDegrees(1, 2),
326 }
327 line1 := PolylineFromLatLngs(latlngs)
328 latlngs = []LatLng{
329 LatLngFromDegrees(0, 0),
330 LatLngFromDegrees(0, 1),
331 LatLngFromDegrees(-1, 0),
332 }
333 line2 := PolylineFromLatLngs(latlngs)
334 tests := []struct {
335 line *Polyline
336 haveLatLng LatLng
337 wantOnRight bool
338 }{
339 {line1, LatLngFromDegrees(-0.5, 0.5), true},
340 {line1, LatLngFromDegrees(0.5, -0.5), false},
341 {line1, LatLngFromDegrees(0.5, 0.5), false},
342 {line1, LatLngFromDegrees(0.5, 1.0), false},
343 {line1, LatLngFromDegrees(-0.5, 2.5), true},
344 {line1, LatLngFromDegrees(1.5, 2.5), true},
345
346
347
348 {line2, LatLngFromDegrees(-0.5, 5.0), false},
349 {line2, LatLngFromDegrees(5.5, 5.0), false},
350 }
351 for _, test := range tests {
352 onRight := test.line.IsOnRight(PointFromLatLng(test.haveLatLng))
353 if onRight != test.wantOnRight {
354 t.Errorf("line.IsOnRight(%v) = %v, want %v", test.haveLatLng, onRight, test.wantOnRight)
355 }
356 }
357 }
358
359 func TestPolylineValidate(t *testing.T) {
360 p := makePolyline("0:0, 2:1, 0:2, 2:3, 0:4, 2:5, 0:6")
361 if p.Validate() != nil {
362 t.Errorf("valid polygon should Validate")
363 }
364
365 p1 := Polyline([]Point{
366 PointFromCoords(0, 1, 0),
367 {r3.Vector{10, 3, 7}},
368 PointFromCoords(0, 0, 1),
369 })
370
371 if err := p1.Validate(); err == nil {
372 t.Errorf("polyline with non-normalized points shouldn't validate")
373 }
374
375
376 p2 := Polyline([]Point{
377 PointFromCoords(0, 1, 0),
378 PointFromCoords(0, 0, 1),
379 PointFromCoords(0, 0, 1),
380 PointFromCoords(1, 0, 0),
381 })
382
383 if err := p2.Validate(); err == nil {
384 t.Errorf("polyline with repeated vertices shouldn't validate")
385 }
386
387 pt := PointFromCoords(1, 1, 0)
388 antiPt := Point{pt.Mul(-1)}
389
390
391 p3 := Polyline([]Point{
392 PointFromCoords(0, 1, 0),
393 pt,
394 PointFromCoords(0, 0, 1),
395 antiPt,
396 })
397
398 if err := p3.Validate(); err != nil {
399 t.Errorf("polyline with non-adjacent antipodal points should validate")
400 }
401
402
403 p4 := Polyline([]Point{
404 PointFromCoords(0, 1, 0),
405 PointFromCoords(0, 0, 1),
406 pt,
407 antiPt,
408 })
409
410 if err := p4.Validate(); err == nil {
411 t.Errorf("polyline with antipodal adjacent points shouldn't validate")
412 }
413 }
414
415 func TestPolylineIntersects(t *testing.T) {
416
417 empty := Polyline{}
418 line := makePolyline("1:1, 4:4")
419 if empty.Intersects(line) {
420 t.Errorf("%v.Intersects(%v) = true, want false", empty, line)
421 }
422
423
424 line1 := makePolyline("1:1, 4:4")
425 line2 := makePolyline("1:1")
426 if line1.Intersects(line2) {
427 t.Errorf("%v.Intersects(%v) = true, want false", line1, line2)
428 }
429
430
431 line3 := makePolyline("1:1, 4:4")
432 smallCrossing := makePolyline("1:2, 2:1")
433 smallNoncrossing := makePolyline("1:2, 2:3")
434 bigCrossing := makePolyline("1:2, 2:3, 4:3")
435 if !line3.Intersects(smallCrossing) {
436 t.Errorf("%v.Intersects(%v) = false, want true", line3, smallCrossing)
437 }
438 if line3.Intersects(smallNoncrossing) {
439 t.Errorf("%v.Intersects(%v) = true, want false", line3, smallNoncrossing)
440 }
441 if !line3.Intersects(bigCrossing) {
442 t.Errorf("%v.Intersects(%v) = false, want true", line3, bigCrossing)
443 }
444
445
446 line4 := makePolyline("1:1, 4:4, 4:6")
447 line5 := makePolyline("1:1, 1:2")
448 line6 := makePolyline("5:1, 4:4, 2:2")
449 if !line4.Intersects(line5) {
450 t.Errorf("%v.Intersects(%v) = false, want true", line4, line5)
451 }
452 if !line4.Intersects(line6) {
453 t.Errorf("%v.Intersects(%v) = false, want true", line4, line6)
454 }
455
456
457 horizontalLeftToRight := makePolyline("0:1, 0:3")
458 verticalBottomToTop := makePolyline("-1:2, 0:2, 1:2")
459 horizontalRightToLeft := makePolyline("0:3, 0:1")
460 verticalTopToBottom := makePolyline("1:2, 0:2, -1:2")
461 if !horizontalLeftToRight.Intersects(verticalBottomToTop) {
462 t.Errorf("%v.Intersects(%v) = false, want true", horizontalLeftToRight, verticalBottomToTop)
463 }
464 if !horizontalLeftToRight.Intersects(verticalTopToBottom) {
465 t.Errorf("%v.Intersects(%v) = false, want true", horizontalLeftToRight, verticalTopToBottom)
466 }
467 if !horizontalRightToLeft.Intersects(verticalBottomToTop) {
468 t.Errorf("%v.Intersects(%v) = false, want true", horizontalRightToLeft, verticalBottomToTop)
469 }
470 if !horizontalRightToLeft.Intersects(verticalTopToBottom) {
471 t.Errorf("%v.Intersects(%v) = false, want true", horizontalRightToLeft, verticalTopToBottom)
472 }
473 }
474
475 func TestPolylineApproxEqual(t *testing.T) {
476 degree := s1.Angle(1) * s1.Degree
477
478 tests := []struct {
479 a, b string
480 maxError s1.Angle
481 want bool
482 }{
483 {
484
485 a: "0:0, 0:10, 5:5",
486 b: "0:0.1, -0.1:9.9, 5:5.2",
487 maxError: 0.5 * degree,
488 want: true,
489 },
490 {
491
492 a: "0:0, 0:10, 5:5",
493 b: "0:0.1, -0.1:9.9, 5:5.2",
494 maxError: 0.01 * degree,
495 want: false,
496 },
497 {
498
499 a: "0:0, 0:10, 0:20",
500 b: "0:0, 0:20",
501 maxError: 0.1 * degree,
502 want: false,
503 },
504 {
505
506 a: "0:0, 5:5, 0:10",
507 b: "5:5, 0:10, 0:0",
508 maxError: 0.1 * degree,
509 want: false,
510 },
511 }
512 for _, test := range tests {
513 a := makePolyline(test.a)
514 b := makePolyline(test.b)
515 if got := a.approxEqual(b, test.maxError); got != test.want {
516 t.Errorf("%v.approxEqual(%v, %v) = %v, want %v", a, b, test.maxError, got, test.want)
517 }
518 }
519 }
520
521 func TestPolylineInterpolate(t *testing.T) {
522 vertices := []Point{PointFromCoords(1, 0, 0),
523 PointFromCoords(0, 1, 0),
524 PointFromCoords(0, 1, 1),
525 PointFromCoords(0, 0, 1),
526 }
527 line := Polyline(vertices)
528
529 want := vertices[0]
530 point, next := line.Interpolate(-0.1)
531 if point != vertices[0] {
532 t.Errorf("%v.Interpolate(%v) = %v, want %v", line, -0.1, point, vertices[0])
533 }
534 if next != 1 {
535 t.Errorf("%v.Interpolate(%v) = %v, want %v", line, -0.1, next, 1)
536 }
537
538 want = PointFromCoords(1, math.Tan(0.2*math.Pi/2.0), 0)
539 if got, _ := line.Interpolate(0.1); !got.ApproxEqual(want) {
540 t.Errorf("%v.Interpolate(%v) = %v, want %v", line, 0.1, got, want)
541 }
542
543 want = PointFromCoords(1, 1, 0)
544 if got, _ := line.Interpolate(0.25); !got.ApproxEqual(want) {
545 t.Errorf("%v.Interpolate(%v) = %v, want %v", line, 0.25, got, want)
546 }
547
548 want = vertices[1]
549 if got, _ := line.Interpolate(0.5); got != want {
550 t.Errorf("%v.Interpolate(%v) = %v, want %v", line, 0.5, got, want)
551 }
552
553 want = vertices[2]
554 point, next = line.Interpolate(0.75)
555 if !point.ApproxEqual(want) {
556 t.Errorf("%v.Interpolate(%v) = %v, want %v", line, 0.75, point, want)
557 }
558 if next != 3 {
559 t.Errorf("%v.Interpolate(%v) = %v, want %v", line, 0.75, next, 3)
560 }
561
562 point, next = line.Interpolate(1.1)
563 if point != vertices[3] {
564 t.Errorf("%v.Interpolate(%v) = %v, want %v", line, 1.1, point, vertices[3])
565 }
566 if next != 4 {
567 t.Errorf("%v.Interpolate(%v) = %v, want %v", line, 1.1, next, 4)
568 }
569
570
571
572 vertices2 := []Point{PointFromCoords(1, 1, 1),
573 PointFromCoords(1, 1, 1+1e-15),
574 PointFromCoords(1, 1, 1+2e-15),
575 }
576 shortLine := Polyline(vertices2)
577
578 point, next = shortLine.Interpolate(1.0 - 2e-16)
579 if point != vertices2[2] {
580 t.Errorf("%v.Interpolate(%v) = %v, want %v", shortLine, 1.0-2e-16, point, vertices2[2])
581 }
582 if next != 3 {
583 t.Errorf("%v.Interpolate(%v) = %v, want %v", shortLine, 1.0-2e-16, next, 3)
584 }
585 }
586
587 func TestPolylineUninterpolate(t *testing.T) {
588 vertices := []Point{PointFromCoords(1, 0, 0)}
589 line := Polyline(vertices)
590 if got, want := line.Uninterpolate(PointFromCoords(0, 1, 0), 1), 0.0; !float64Eq(got, want) {
591 t.Errorf("Uninterpolate on a polyline with 2 or fewer vertices should return 0, got %v", got)
592 }
593
594 vertices = append(vertices,
595 PointFromCoords(0, 1, 0),
596 PointFromCoords(0, 1, 1),
597 PointFromCoords(0, 0, 1),
598 )
599 line = Polyline(vertices)
600
601 interpolated, nextVertex := line.Interpolate(-0.1)
602 if got, want := line.Uninterpolate(interpolated, nextVertex), 0.0; !float64Eq(got, want) {
603 t.Errorf("line.Uninterpolate(%v, %d) = %v, want %v", interpolated, nextVertex, got, want)
604 }
605 interpolated, nextVertex = line.Interpolate(0.0)
606 if got, want := line.Uninterpolate(interpolated, nextVertex), 0.0; !float64Eq(got, want) {
607 t.Errorf("line.Uninterpolate(%v, %d) = %v, want %v", interpolated, nextVertex, got, want)
608 }
609 interpolated, nextVertex = line.Interpolate(0.5)
610 if got, want := line.Uninterpolate(interpolated, nextVertex), 0.5; !float64Eq(got, want) {
611 t.Errorf("line.Uninterpolate(%v, %d) = %v, want %v", interpolated, nextVertex, got, want)
612 }
613 interpolated, nextVertex = line.Interpolate(0.75)
614 if got, want := line.Uninterpolate(interpolated, nextVertex), 0.75; !float64Eq(got, want) {
615 t.Errorf("line.Uninterpolate(%v, %d) = %v, want %v", interpolated, nextVertex, got, want)
616 }
617 interpolated, nextVertex = line.Interpolate(1.1)
618 if got, want := line.Uninterpolate(interpolated, nextVertex), 1.0; !float64Eq(got, want) {
619 t.Errorf("line.Uninterpolate(%v, %d) = %v, want %v", interpolated, nextVertex, got, want)
620 }
621
622
623 if got, want := line.Uninterpolate(PointFromCoords(0, 1, 0), len(line)), 1.0; !float64Eq(got, want) {
624 t.Errorf("line.Uninterpolate(%v, %d) = %v, want %v", PointFromCoords(0, 1, 0), len(line), got, want)
625 }
626 }
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
View as plain text