aboutsummaryrefslogtreecommitdiff
path: root/climb/profile_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'climb/profile_test.go')
-rw-r--r--climb/profile_test.go113
1 files changed, 113 insertions, 0 deletions
diff --git a/climb/profile_test.go b/climb/profile_test.go
new file mode 100644
index 0000000..1342916
--- /dev/null
+++ b/climb/profile_test.go
@@ -0,0 +1,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()))
+}