diff options
| author | Martin Kagamino Lehoux <martin@lehoux.net> | 2025-04-21 10:54:11 +0200 |
|---|---|---|
| committer | Martin Kagamino Lehoux <martin@lehoux.net> | 2025-04-21 10:54:11 +0200 |
| commit | 8167681a317efa7b5c5176764c12e1c462f47803 (patch) | |
| tree | ca4114699701c5e900c77abc12d2ea7977d7927a /ride | |
| parent | 87850541aa6f719721209ed994b93ad4d7ab4077 (diff) | |
First implem of similar KOM
Diffstat (limited to 'ride')
| -rw-r--r-- | ride/.snapshots/TestRideClimbLaCride | 4 | ||||
| -rw-r--r-- | ride/climb.go | 31 | ||||
| -rw-r--r-- | ride/index.go | 36 | ||||
| -rw-r--r-- | ride/index_test.go | 36 | ||||
| -rw-r--r-- | ride/ride.go | 27 |
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") } |