1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
|
package climb
import (
"math"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// stretch describes one [lengthM, slopePercent] stretch of the buildProfile trace.
type stretch struct {
lengthM float64
slopePercent float64
}
// buildProfile lays out one recorded point every 50 m along the given stretches.
func buildProfile(stretches ...stretch) Route {
route := Route{Distances: []float64{0}, Elevations: []float64{0}}
distanceM, elevationM := 0.0, 0.0
for _, stretch := range stretches {
pointCount := int(math.Round(stretch.lengthM / 50))
for index := 1; index <= pointCount; index++ {
stepM := stretch.lengthM * float64(index) / float64(pointCount)
route.Distances = append(route.Distances, distanceM+stepM)
route.Elevations = append(route.Elevations, elevationM+stepM*stretch.slopePercent/100)
}
distanceM += stretch.lengthM
elevationM += stretch.lengthM * stretch.slopePercent / 100
}
return route
}
func TestProfileSectionsKeepsShortRamp(t *testing.T) {
route := buildProfile(stretch{300, 4}, stretch{300, 12}, stretch{300, 4})
sections := route.ProfileSections(DefaultProfilePolicy())
require.Len(t, sections, 3)
assert.InDelta(t, 4, sections[0].SlopePercent, 0.01)
assert.InDelta(t, 12, sections[1].SlopePercent, 0.01)
assert.InDelta(t, 4, sections[2].SlopePercent, 0.01)
assert.InDelta(t, 300, sections[1].EndDistanceM-sections[1].StartDistanceM, 0.01)
}
func TestProfileSectionsCollapsesConstantSlope(t *testing.T) {
route := buildProfile(stretch{5000, 6})
sections := route.ProfileSections(DefaultProfilePolicy())
require.Len(t, sections, 1)
assert.InDelta(t, 6, sections[0].SlopePercent, 0.01)
assert.Zero(t, sections[0].StartDistanceM)
assert.InDelta(t, 5000, sections[0].EndDistanceM, 0.01)
}
func TestProfileSectionsReportsDescent(t *testing.T) {
route := buildProfile(stretch{2000, -5})
sections := route.ProfileSections(DefaultProfilePolicy())
require.Len(t, sections, 1)
assert.InDelta(t, -5, sections[0].SlopePercent, 0.01)
}
func TestProfileSectionsHonoursBudget(t *testing.T) {
route := Route{}
for index := 0; index <= 200; index++ {
distanceM := float64(index) * 50
route.Distances = append(route.Distances, distanceM)
route.Elevations = append(route.Elevations, distanceM*0.05+math.Sin(float64(index))*4)
}
sections := route.ProfileSections(DefaultProfilePolicy())
assert.Greater(t, len(sections), 10)
assert.LessOrEqual(t, len(sections), DefaultProfilePolicy().MaxSections)
budgeted := route.ProfileSections(ProfilePolicy{MaxSections: 5, MinBendM: 1, BendPerBaseStep: 0.01})
assert.LessOrEqual(t, len(budgeted), 5)
}
func TestProfileSectionsKeepsAbsoluteDistances(t *testing.T) {
route := buildProfile(stretch{2000, 8})
for index := range route.Distances {
route.Distances[index] += 5000
}
sections := route.ProfileSections(DefaultProfilePolicy())
require.Len(t, sections, 1)
assert.InDelta(t, 5000, sections[0].StartDistanceM, 0.01)
assert.InDelta(t, 7000, sections[0].EndDistanceM, 0.01)
}
func TestProfileSectionsReadsRouteSlice(t *testing.T) {
route := buildProfile(stretch{1000, 20}, stretch{1000, 8}, stretch{1000, 20})
start := 20
end := 40
sections := route.Slice(start, end).ProfileSections(DefaultProfilePolicy())
require.Len(t, sections, 1)
assert.InDelta(t, 8, sections[0].SlopePercent, 0.01)
assert.InDelta(t, route.Distances[start], sections[0].StartDistanceM, 0.01)
assert.InDelta(t, route.Distances[end], sections[0].EndDistanceM, 0.01)
}
func TestProfileSectionsRejectsShortRoute(t *testing.T) {
assert.Nil(t, Route{}.ProfileSections(DefaultProfilePolicy()))
assert.Nil(t, Route{Distances: []float64{0}, Elevations: []float64{0}}.ProfileSections(DefaultProfilePolicy()))
assert.Nil(t, Route{Distances: []float64{0, 10}, Elevations: []float64{0}}.ProfileSections(DefaultProfilePolicy()))
}
|