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 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 }
|