summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMartin Kagamino Lehoux <martin@lehoux.net>2026-08-14 19:49:18 +0200
committerMartin Kagamino Lehoux <martin@lehoux.net>2026-08-14 19:49:18 +0200
commit9b62eec13fb391a3055253422cc58252db820d99 (patch)
tree469e4b1d64258bd226ffe97dfc481eaffc72aa07
parent3eebdfbe4a6033a0bef0acdaa13061dd830c65d6 (diff)
refactor: extract ride analysis
-rw-r--r--rideanalysis/analysis.go95
-rw-r--r--web/views.go81
2 files changed, 114 insertions, 62 deletions
diff --git a/rideanalysis/analysis.go b/rideanalysis/analysis.go
new file mode 100644
index 0000000..3d34855
--- /dev/null
+++ b/rideanalysis/analysis.go
@@ -0,0 +1,95 @@
+package rideanalysis
+
+import (
+ "math"
+
+ "github.com/jftuga/geodist"
+ "github.com/martinlehoux/biking_home/mountain_pass"
+ "github.com/martinlehoux/biking_home/official_climb"
+ "github.com/martinlehoux/biking_home/ride"
+)
+
+type Result struct {
+ Climbs []AnalyzedClimb
+ Crossings []mountain_pass.Crossing
+}
+
+type AnalyzedClimb struct {
+ Segment ride.Climb
+ Name string
+ Score float64
+ Category string
+ DistanceM float64
+ SlopePercent float64
+ Cotacol float64
+ OfficialClimbID int64
+ OfficialName string
+}
+
+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),
+ }
+ for _, detectedClimb := range parsed.AllClimbs() {
+ displayClimb := detectedClimb
+ if matchedPass, ok := mountain_pass.MatchClimb(displayClimb, passes, 300, 50); ok {
+ displayClimb.Name = matchedPass.Name
+ }
+ matchedOfficial, officialFound := official_climb.MatchClimb(detectedClimb, officialClimbs, matchPolicy)
+ if officialFound {
+ startIndex := nearestCoordIndex(parsed, matchedOfficial.StartCoord)
+ endIndex := nearestCoordIndex(parsed, matchedOfficial.EndCoord)
+ if startIndex < endIndex {
+ displayClimb = parsed.ClimbFromIndexes(startIndex, endIndex)
+ displayClimb.Name = matchedOfficial.Name
+ } else {
+ officialFound = false
+ matchedOfficial = official_climb.OfficialClimb{}
+ }
+ }
+ score := displayClimb.Score()
+ result.Climbs = append(result.Climbs, AnalyzedClimb{
+ Segment: displayClimb,
+ Name: displayClimb.Name,
+ Score: score,
+ Category: ride.Category(score),
+ DistanceM: displayClimb.EndDistanceM() - displayClimb.StartDistanceM(),
+ SlopePercent: ride.Slope(parsed, displayClimb.StartIndex(), displayClimb.EndIndex()) * 100,
+ Cotacol: displayClimb.DifficultyScore(),
+ OfficialClimbID: officialClimbID(matchedOfficial, officialFound),
+ OfficialName: officialClimbName(matchedOfficial, officialFound),
+ })
+ }
+ for _, crossing := range mountain_pass.DetectCrossings(parsed, passes, 100, 25) {
+ result.Crossings = append(result.Crossings, crossing)
+ }
+ return result
+}
+
+func nearestCoordIndex(parsed ride.Ride, target geodist.Coord) int {
+ bestIndex := 0
+ bestDistance := math.Inf(1)
+ for index := 0; index < parsed.Len(); index++ {
+ _, distanceKm := geodist.HaversineDistance(parsed.Coord(index), target)
+ if distanceKm < bestDistance {
+ bestDistance = distanceKm
+ bestIndex = index
+ }
+ }
+ return bestIndex
+}
+
+func officialClimbID(climb official_climb.OfficialClimb, found bool) int64 {
+ if !found {
+ return 0
+ }
+ return climb.ID
+}
+
+func officialClimbName(climb official_climb.OfficialClimb, found bool) string {
+ if !found {
+ return ""
+ }
+ return climb.Name
+}
diff --git a/web/views.go b/web/views.go
index 310c39c..79356d2 100644
--- a/web/views.go
+++ b/web/views.go
@@ -2,14 +2,13 @@ package web
import (
"fmt"
- "math"
"net/url"
"time"
- "github.com/jftuga/geodist"
"github.com/martinlehoux/biking_home/mountain_pass"
"github.com/martinlehoux/biking_home/official_climb"
"github.com/martinlehoux/biking_home/ride"
+ "github.com/martinlehoux/biking_home/rideanalysis"
"github.com/martinlehoux/biking_home/rides"
)
@@ -239,42 +238,27 @@ func buildRideProfile(parsed ride.Ride, passes []mountain_pass.MountainPass, off
Longitude: coordinate.Lon,
}
}
- climbs := parsed.AllClimbs()
- for i := range climbs {
- displayClimb := climbs[i]
- if matchedPass, ok := mountain_pass.MatchClimb(displayClimb, passes, 300, 50); ok {
- displayClimb.Name = matchedPass.Name
- }
- matchedOfficial, officialFound := official_climb.MatchClimb(climbs[i], officialClimbs, matchPolicy)
- if officialFound {
- startIndex := nearestCoordIndex(parsed, matchedOfficial.StartCoord)
- endIndex := nearestCoordIndex(parsed, matchedOfficial.EndCoord)
- if startIndex < endIndex {
- displayClimb = parsed.ClimbFromIndexes(startIndex, endIndex)
- displayClimb.Name = matchedOfficial.Name
- } else {
- officialFound = false
- matchedOfficial = official_climb.OfficialClimb{}
- }
- }
+ analysis := rideanalysis.Analyze(parsed, passes, officialClimbs, matchPolicy)
+ for _, analyzedClimb := range analysis.Climbs {
+ climb := analyzedClimb.Segment
profile.Climbs = append(profile.Climbs, RideProfileClimb{
- StartKm: displayClimb.StartDistanceM() / 1000,
- EndKm: displayClimb.EndDistanceM() / 1000,
- TopKm: displayClimb.TopDistanceM() / 1000,
- TopElevationM: displayClimb.TopElevationM(),
- Name: displayClimb.Name,
- Score: displayClimb.Score(),
- Category: ride.Category(displayClimb.Score()),
- DistanceKm: (displayClimb.EndDistanceM() - displayClimb.StartDistanceM()) / 1000,
- SlopePercent: ride.Slope(parsed, displayClimb.StartIndex(), displayClimb.EndIndex()) * 100,
- Cotacol: displayClimb.DifficultyScore(),
- OfficialClimbID: officialClimbID(matchedOfficial, officialFound),
- OfficialName: officialClimbName(matchedOfficial, officialFound),
- StartIndex: displayClimb.StartIndex(),
- EndIndex: displayClimb.EndIndex(),
+ StartKm: climb.StartDistanceM() / 1000,
+ EndKm: climb.EndDistanceM() / 1000,
+ TopKm: climb.TopDistanceM() / 1000,
+ TopElevationM: climb.TopElevationM(),
+ Name: analyzedClimb.Name,
+ Score: analyzedClimb.Score,
+ Category: analyzedClimb.Category,
+ DistanceKm: analyzedClimb.DistanceM / 1000,
+ SlopePercent: analyzedClimb.SlopePercent,
+ Cotacol: analyzedClimb.Cotacol,
+ OfficialClimbID: analyzedClimb.OfficialClimbID,
+ OfficialName: analyzedClimb.OfficialName,
+ StartIndex: climb.StartIndex(),
+ EndIndex: climb.EndIndex(),
})
}
- for _, crossing := range mountain_pass.DetectCrossings(parsed, passes, 100, 25) {
+ for _, crossing := range analysis.Crossings {
profile.Crossings = append(profile.Crossings, RideProfileCrossing{
DistanceKm: crossing.RideDistanceM / 1000,
PassElevation: float64(crossing.Pass.Elevation),
@@ -287,33 +271,6 @@ func buildRideProfile(parsed ride.Ride, passes []mountain_pass.MountainPass, off
return profile
}
-func nearestCoordIndex(parsed ride.Ride, target geodist.Coord) int {
- bestIndex := 0
- bestDistance := math.Inf(1)
- for index := 0; index < parsed.Len(); index++ {
- _, distanceKm := geodist.HaversineDistance(parsed.Coord(index), target)
- if distanceKm < bestDistance {
- bestDistance = distanceKm
- bestIndex = index
- }
- }
- return bestIndex
-}
-
-func officialClimbID(climb official_climb.OfficialClimb, found bool) int64 {
- if !found {
- return 0
- }
- return climb.ID
-}
-
-func officialClimbName(climb official_climb.OfficialClimb, found bool) string {
- if !found {
- return ""
- }
- return climb.Name
-}
-
type SyncPageData struct {
From string
To string