summaryrefslogtreecommitdiff
path: root/ride
diff options
context:
space:
mode:
authorMartin Kagamino Lehoux <martin@lehoux.net>2025-04-21 10:54:11 +0200
committerMartin Kagamino Lehoux <martin@lehoux.net>2025-04-21 10:54:11 +0200
commit8167681a317efa7b5c5176764c12e1c462f47803 (patch)
treeca4114699701c5e900c77abc12d2ea7977d7927a /ride
parent87850541aa6f719721209ed994b93ad4d7ab4077 (diff)
First implem of similar KOM
Diffstat (limited to 'ride')
-rw-r--r--ride/.snapshots/TestRideClimbLaCride4
-rw-r--r--ride/climb.go31
-rw-r--r--ride/index.go36
-rw-r--r--ride/index_test.go36
-rw-r--r--ride/ride.go27
5 files changed, 123 insertions, 11 deletions
diff --git a/ride/.snapshots/TestRideClimbLaCride b/ride/.snapshots/TestRideClimbLaCride
new file mode 100644
index 0000000..386c553
--- /dev/null
+++ b/ride/.snapshots/TestRideClimbLaCride
@@ -0,0 +1,4 @@
+([]string) (len=2) {
+ (string) (len=69) "Found similar climbs to 17.1km-18.8km: 1.7km at 4.8% (38 pts - Cat 4)",
+ (string) (len=47) " 17.2km-18.8km: 1.6km at 4.9% (38 pts - Cat 4)"
+}
diff --git a/ride/climb.go b/ride/climb.go
index d2d5d0e..0a9994d 100644
--- a/ride/climb.go
+++ b/ride/climb.go
@@ -14,9 +14,20 @@ import (
const ClimbDistanceMinimum = 500
type Climb struct {
- start int
- end int
- points []Point
+ rideStart int
+ rideEnd int
+ points []Point
+}
+
+func (climb Climb) Duration() time.Duration {
+ return climb.points[len(climb.points)-1].Timestamp.Sub(climb.points[0].Timestamp)
+}
+func (climb Climb) Start() Point {
+ return climb.points[0]
+}
+
+func (climb Climb) End() Point {
+ return climb.points[len(climb.points)-1]
}
func (climb Climb) String() string {
@@ -92,9 +103,9 @@ func bestClimbBetween(points []Point, start int, end int) Climb {
bestScore = score
}
}
- climb := Climb{start: bestStart, end: bestEnd, points: points[bestStart : bestEnd+1]}
+ kcore.Assert(bestStart < bestEnd, "empty climb")
+ climb := Climb{rideStart: bestStart, rideEnd: bestEnd, points: points[bestStart : bestEnd+1]}
- kcore.Assert(climb.start < climb.end, "empty climb")
return climb
}
@@ -115,12 +126,12 @@ func climbsBetween(points []Point, start int, end int) []Climb {
return climbsBetween(points, start+1, end)
}
climb := bestClimbBetween(points, start, highest)
- if Score(points, climb.start, climb.end) >= 35 && points[climb.end].DistanceM-points[climb.start].DistanceM >= ClimbDistanceMinimum {
- slog.Debug("Found climb between", slog.Int("start", int(points[climb.start].DistanceM)), slog.Int("end", int(points[climb.end].DistanceM)))
+ if Score(points, climb.rideStart, climb.rideEnd) >= 35 && points[climb.rideEnd].DistanceM-points[climb.rideStart].DistanceM >= ClimbDistanceMinimum {
+ slog.Debug("Found climb between", slog.Int("start", int(points[climb.rideStart].DistanceM)), slog.Int("end", int(points[climb.rideEnd].DistanceM)))
climbs = append(climbs, climb)
}
- climbs = append(climbs, climbsBetween(points, start, climb.start)...)
- climbs = append(climbs, climbsBetween(points, climb.end, end)...)
+ climbs = append(climbs, climbsBetween(points, start, climb.rideStart)...)
+ climbs = append(climbs, climbsBetween(points, climb.rideEnd, end)...)
return climbs
}
@@ -131,4 +142,4 @@ func (ride *Ride) AllClimbs() []Climb {
return climbs
}
-func climbCmpStart(a, b Climb) int { return a.start - b.start }
+func climbCmpStart(a, b Climb) int { return a.rideStart - b.rideStart }
diff --git a/ride/index.go b/ride/index.go
new file mode 100644
index 0000000..18e2ac8
--- /dev/null
+++ b/ride/index.go
@@ -0,0 +1,36 @@
+package ride
+
+import "github.com/jftuga/geodist"
+
+type RideClimbsIndex interface {
+ Insert(ride Ride)
+ Similar(ride Ride, climb Climb) []Climb
+}
+
+type FlatRideClimbsIndex struct {
+ sensibility float64
+ climbs []Climb
+}
+
+func NewFlatRideClimbsIndex(sensibility float64) *FlatRideClimbsIndex {
+ return &FlatRideClimbsIndex{
+ climbs: make([]Climb, 0),
+ sensibility: sensibility,
+ }
+}
+
+func (index *FlatRideClimbsIndex) Insert(ride Ride) {
+ index.climbs = append(index.climbs, ride.AllClimbs()...)
+}
+
+func (index *FlatRideClimbsIndex) Similar(ride Ride, climb Climb) []Climb {
+ results := make([]Climb, 0)
+ for _, c := range index.climbs {
+ _, startDistance := geodist.HaversineDistance(c.Start().Coord, climb.Start().Coord)
+ _, endDistance := geodist.HaversineDistance(c.End().Coord, climb.End().Coord)
+ if startDistance <= index.sensibility/1000 && endDistance <= index.sensibility/1000 {
+ results = append(results, c)
+ }
+ }
+ return results
+}
diff --git a/ride/index_test.go b/ride/index_test.go
new file mode 100644
index 0000000..e95f030
--- /dev/null
+++ b/ride/index_test.go
@@ -0,0 +1,36 @@
+package ride_test
+
+import (
+ "fmt"
+ "testing"
+
+ "github.com/bradleyjkemp/cupaloy"
+ "github.com/martinlehoux/biking_home/ride"
+ "github.com/stretchr/testify/assert"
+ "github.com/tkrajina/gpxgo/gpx"
+)
+
+func TestRideClimbLaCride(t *testing.T) {
+ gpxContent, err := gpx.ParseFile("../examples/activity_18043356988.gpx")
+ assert.NoError(t, err)
+ ride19Jan := ride.FromGPX(gpxContent)
+ gpxContent, err = gpx.ParseFile("../examples/activity_18679866717.gpx")
+ assert.NoError(t, err)
+ ride30Mar := ride.FromGPX(gpxContent)
+ gpxContent, err = gpx.ParseFile("../examples/activity_18880605641.gpx")
+ assert.NoError(t, err)
+ ride20Apr := ride.FromGPX(gpxContent)
+ index := ride.NewFlatRideClimbsIndex(30)
+ index.Insert(ride19Jan)
+ index.Insert(ride30Mar)
+ logs := []string{}
+ for _, climb := range ride20Apr.AllClimbs() {
+ if len(index.Similar(ride20Apr, climb)) > 0 {
+ logs = append(logs, fmt.Sprintf("Found similar climbs to %s", climb))
+ for _, similar := range index.Similar(ride20Apr, climb) {
+ logs = append(logs, fmt.Sprintf(" %s", similar))
+ }
+ }
+ }
+ cupaloy.SnapshotT(t, logs)
+}
diff --git a/ride/ride.go b/ride/ride.go
index c8ecacc..fb774e4 100644
--- a/ride/ride.go
+++ b/ride/ride.go
@@ -4,6 +4,9 @@ import (
"github.com/jftuga/geodist"
"github.com/martinlehoux/kagamigo/kcore"
"github.com/tkrajina/gpxgo/gpx"
+ "gonum.org/v1/plot"
+ "gonum.org/v1/plot/plotter"
+ "gonum.org/v1/plot/vg"
)
type Ride struct {
@@ -63,5 +66,27 @@ func (r *Ride) ClimbFromDist(startDist, endDist float64) Climb {
break
}
}
- return Climb{start: start, end: end, points: r.points[start : end+1]}
+ return Climb{rideStart: start, rideEnd: end, points: r.points[start : end+1]}
+}
+
+func Plot(r *Ride, outputFile string) {
+ r.check()
+
+ pts := make(plotter.XYs, len(r.points))
+ for i, p := range r.points {
+ pts[i].X = p.DistanceM / 1000 // Convert distance to kilometers
+ pts[i].Y = p.ElevationM
+ }
+
+ p := plot.New()
+ p.Title.Text = "Ride Elevation Profile"
+ p.X.Label.Text = "Distance (km)"
+ p.Y.Label.Text = "Elevation (m)"
+
+ line, err := plotter.NewLine(pts)
+ kcore.Expect(err, "failed to create line plot")
+ p.Add(line)
+
+ err = p.Save(10*vg.Inch, 4*vg.Inch, outputFile)
+ kcore.Expect(err, "failed to save plot")
}