diff options
| author | Martin Kagamino Lehoux <martin@lehoux.net> | 2024-07-28 17:50:29 +0200 |
|---|---|---|
| committer | Martin Kagamino Lehoux <martin@lehoux.net> | 2024-07-28 17:50:29 +0200 |
| commit | 203b9bdf17bd4742b5aab2c35df47b59af0652f1 (patch) | |
| tree | b6bc8ddee21bcaa022ef68b3769f61100117b190 /ride/climb.go | |
| parent | 171366f337c3422331b84b6c7416e1b1d6bd36a0 (diff) | |
test: Add some test to fix behavior
Diffstat (limited to 'ride/climb.go')
| -rw-r--r-- | ride/climb.go | 113 |
1 files changed, 113 insertions, 0 deletions
diff --git a/ride/climb.go b/ride/climb.go new file mode 100644 index 0000000..a337a15 --- /dev/null +++ b/ride/climb.go @@ -0,0 +1,113 @@ +package ride + +import ( + "fmt" + "math" + "slices" + + "github.com/martinlehoux/kagamigo/kcore" +) + +const ClimbDistanceMinimum = 500 + +type Climb struct { + start int + end int +} + +type Point struct { + distance float64 + elevation float64 +} + +func NewPoint(distance float64, elevation float64) Point { + return Point{ + distance: distance, + elevation: elevation, + } +} + +func Slope(start, end Point) float64 { + return (end.elevation - start.elevation) / (end.distance - start.distance) +} + +func Score(points []Point, start int, end int) float64 { + kcore.Assert(end > start, "no points for score") + distance := points[end].distance - points[start].distance + if distance == 0 { + return 0 + } + dElevation := points[end].elevation - points[start].elevation + + return math.Abs(dElevation) * dElevation / distance * 100.0 * 100.0 / 1000.0 +} + +func Category(score float64) string { + switch { + case score < 35: + return "NO" + case score < 80: + return "Cat 4" + case score < 180: + return "Cat 3" + case score < 250: + return "Cat 2" + case score < 600: + return "Cat 1" + default: + return "HC" + } +} + +func bestClimbUntilEnd(points []Point, start int, end int) Climb { + kcore.Assert(end > start, "empty points") + + bestScore := Score(points, start, end) + bestStart := start + for i := start; i < end; i++ { + score := Score(points, i, end) + if score > bestScore { + bestStart = i + bestScore = score + } + } + climb := Climb{bestStart, end} + + kcore.Assert(climb.start < climb.end, "empty climb") + return climb +} + +func climbsBetween(points []Point, start int, end int) []Climb { + climbs := []Climb{} + if points[end].distance-points[start].distance < ClimbDistanceMinimum { + return climbs + } + fmt.Printf("Searching climbs between %.1fkm and %.1fkm\n", points[start].distance/1000, points[end].distance/1000) + highest := start + for i := start; i < end; i++ { + if points[i].elevation > points[highest].elevation { + highest = i + } + } + // TODO: Use descent to reduce recursion + if highest == start { + return climbsBetween(points, start+1, end) + } + climb := bestClimbUntilEnd(points, start, highest) + if Score(points, climb.start, climb.end) >= 35 { + fmt.Printf("Found climb between %.1fkm and %.1fkm\n", points[climb.start].distance/1000, points[climb.end].distance/1000) + climbs = append(climbs, climb) + } + climbs = append(climbs, climbsBetween(points, start, climb.start)...) + climbs = append(climbs, climbsBetween(points, climb.end, end)...) + + return climbs +} + +func (ride *Ride) AllClimbs() []Climb { + climbs := climbsBetween(ride.points, 0, len(ride.points)-1) + slices.SortFunc(climbs, climbCmpStart) + return climbs +} + +func climbCmpStart(a, b Climb) int { return a.start - b.start } |