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
|
package rideanalysis
import (
"slices"
"github.com/martinlehoux/biking_home/climb"
"github.com/martinlehoux/biking_home/core"
"github.com/martinlehoux/biking_home/mountain_pass"
"github.com/martinlehoux/biking_home/official_climb"
"github.com/martinlehoux/biking_home/ride"
)
const (
passMatchRadiusM = 300
passMatchElevationToleranceM = 50
passCrossingRadiusM = 100
passCrossingElevationToleranceM = 25
)
type Result struct {
Climbs []AnalyzedClimb
Crossings []mountain_pass.Crossing
}
type AnalyzedClimb struct {
Segment climb.Climb
Name string
Category string
DistanceM float64
SlopePercent float64
Cotacol float64
OfficialClimbID int64
OfficialName string
Interval core.Interval
}
func Analyze(parsed ride.Ride, passes []mountain_pass.MountainPass, officialClimbs []official_climb.OfficialClimb, matchPolicy official_climb.MatchPolicy) Result {
result := Result{
Climbs: make([]AnalyzedClimb, 0),
Crossings: make([]mountain_pass.Crossing, 0),
}
claimedClimbs := make([]AnalyzedClimb, 0)
claimedRanges := make([]core.Interval, 0)
for _, official := range officialClimbs {
for _, matched := range official_climb.MatchClimbs(parsed.Coords, official, matchPolicy.EndpointRadiusM) {
interval := core.NewInterval(matched.StartIndex, matched.EndIndex)
claimedRanges = append(claimedRanges, interval)
claimedClimbs = append(claimedClimbs, analyzedClimb(routeClimb(parsed.Route, interval), interval, official.Name, official.ID))
}
}
slices.SortFunc(claimedRanges, func(a, b core.Interval) int { return a.Start - b.Start })
for _, detected := range climb.Detect(parsed.Route, claimedRanges, climb.ClimbSlopeMinimum) {
name := ""
if matchedPass, ok := mountain_pass.MatchClimb(detected.Climb, passes, passMatchRadiusM, passMatchElevationToleranceM); ok {
name = matchedPass.Name
}
interval := core.NewInterval(detected.Start, detected.Start+len(detected.Climb.Distances)-1)
result.Climbs = append(result.Climbs, analyzedClimb(detected.Climb, interval, name, 0))
}
climbs := make([]AnalyzedClimb, 0, len(claimedClimbs)+len(result.Climbs))
climbs = append(climbs, claimedClimbs...)
climbs = append(climbs, result.Climbs...)
slices.SortFunc(climbs, func(a, b AnalyzedClimb) int {
return a.Interval.Start - b.Interval.Start
})
result.Climbs = climbs
for _, crossing := range mountain_pass.DetectCrossings(parsed.Coords, parsed.Distances, parsed.Elevations, passes, passCrossingRadiusM, passCrossingElevationToleranceM) {
result.Crossings = append(result.Crossings, crossing)
}
return result
}
// routeClimb returns the climb segment of route covered by interval.
func routeClimb(route climb.Route, interval core.Interval) climb.Climb {
return climb.Climb{
Distances: route.Distances[interval.Start : interval.End+1],
Elevations: route.Elevations[interval.Start : interval.End+1],
Coords: route.Coords[interval.Start : interval.End+1],
}
}
func analyzedClimb(segment climb.Climb, interval core.Interval, name string, officialID int64) AnalyzedClimb {
cotacol := segment.DifficultyScore()
return AnalyzedClimb{
Segment: segment,
Name: name,
Category: climb.CategoryOf(cotacol).String(),
DistanceM: segment.Distances[len(segment.Distances)-1] - segment.Distances[0],
SlopePercent: climb.Slope(segment.Distances, segment.Elevations) * 100,
Cotacol: cotacol,
OfficialClimbID: officialID,
OfficialName: officialClimbNameForID(name, officialID),
Interval: interval,
}
}
func officialClimbNameForID(name string, officialID int64) string {
if officialID == 0 {
return ""
}
return name
}
|