summaryrefslogtreecommitdiff
path: root/ride
diff options
context:
space:
mode:
authorMartin Kagamino Lehoux <martin@lehoux.net>2025-04-21 10:46:17 +0200
committerMartin Kagamino Lehoux <martin@lehoux.net>2025-04-21 10:46:17 +0200
commit87850541aa6f719721209ed994b93ad4d7ab4077 (patch)
treeb7aeb56f6cacabf6bf80e7c6ae142ca176046aa4 /ride
parent81645d9a6995bb2f44019948b3f45c277772da5d (diff)
Add more data in climb & point
Diffstat (limited to 'ride')
-rw-r--r--ride/climb.go46
-rw-r--r--ride/climb_test.go60
-rw-r--r--ride/ride.go22
3 files changed, 64 insertions, 64 deletions
diff --git a/ride/climb.go b/ride/climb.go
index 190225e..d2d5d0e 100644
--- a/ride/climb.go
+++ b/ride/climb.go
@@ -1,43 +1,49 @@
package ride
import (
+ "fmt"
"log/slog"
"math"
"slices"
+ "time"
+ "github.com/jftuga/geodist"
"github.com/martinlehoux/kagamigo/kcore"
)
const ClimbDistanceMinimum = 500
type Climb struct {
- start int
- end int
+ start int
+ end int
+ points []Point
}
-type Point struct {
- distance float64
- elevation float64
+func (climb Climb) String() string {
+ start := climb.points[0]
+ end := climb.points[len(climb.points)-1]
+ score := Score(climb.points, 0, len(climb.points)-1)
+ return fmt.Sprintf("%.1fkm-%.1fkm: %.1fkm at %.1f%% (%d pts - %s)", start.DistanceM/1000, end.DistanceM/1000, (end.DistanceM-start.DistanceM)/1000, Slope(start, end)*100, int(score), Category(score))
}
-func NewPoint(distance float64, elevation float64) Point {
- return Point{
- distance: distance,
- elevation: elevation,
- }
+type Point struct {
+ DistanceM float64
+ ElevationM float64
+ Coord geodist.Coord
+ Timestamp time.Time
}
func Slope(start, end Point) float64 {
- return (end.elevation - start.elevation) / (end.distance - start.distance)
+ return (end.ElevationM - start.ElevationM) / (end.DistanceM - start.DistanceM)
}
func Score(points []Point, start int, end int) float64 {
kcore.Assert(end > start, "no points for score")
- distance := points[end].distance - points[start].distance
+ distance := points[end].DistanceM - points[start].DistanceM
if distance == 0 {
return 0
}
- dElevation := points[end].elevation - points[start].elevation
+ dElevation := points[end].ElevationM - points[start].ElevationM
return math.Abs(dElevation) * dElevation / distance * 100.0 * 100.0 / 1000.0
}
@@ -86,7 +92,7 @@ func bestClimbBetween(points []Point, start int, end int) Climb {
bestScore = score
}
}
- climb := Climb{bestStart, bestEnd}
+ climb := Climb{start: bestStart, end: bestEnd, points: points[bestStart : bestEnd+1]}
kcore.Assert(climb.start < climb.end, "empty climb")
return climb
@@ -94,23 +100,23 @@ func bestClimbBetween(points []Point, start int, end int) Climb {
func climbsBetween(points []Point, start int, end int) []Climb {
climbs := []Climb{}
- if points[end].distance-points[start].distance < ClimbDistanceMinimum {
+ if points[end].DistanceM-points[start].DistanceM < ClimbDistanceMinimum {
return climbs
}
- slog.Debug("Searching climbs between", slog.Int("start", int(points[start].distance)), slog.Int("end", int(points[end].distance)))
+ slog.Debug("Searching climbs between", slog.Int("start", int(points[start].DistanceM)), slog.Int("end", int(points[end].DistanceM)))
highest := start
for i := start; i <= end; i++ {
- if points[i].elevation > points[highest].elevation {
+ if points[i].ElevationM > points[highest].ElevationM {
highest = i
}
}
// TODO: Use descent to reduce recursion
- if points[highest].distance-points[start].distance < ClimbDistanceMinimum {
+ if points[highest].DistanceM-points[start].DistanceM < ClimbDistanceMinimum {
return climbsBetween(points, start+1, end)
}
climb := bestClimbBetween(points, start, highest)
- if Score(points, climb.start, climb.end) >= 35 && points[climb.end].distance-points[climb.start].distance >= ClimbDistanceMinimum {
- slog.Debug("Found climb between", slog.Int("start", int(points[climb.start].distance)), slog.Int("end", int(points[climb.end].distance)))
+ 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)))
climbs = append(climbs, climb)
}
climbs = append(climbs, climbsBetween(points, start, climb.start)...)
diff --git a/ride/climb_test.go b/ride/climb_test.go
index 803b67a..75aeb0e 100644
--- a/ride/climb_test.go
+++ b/ride/climb_test.go
@@ -4,6 +4,7 @@ import (
"strconv"
"strings"
"testing"
+ "time"
"github.com/martinlehoux/biking_home/ride"
"github.com/martinlehoux/kagamigo/kcore"
@@ -40,7 +41,7 @@ func (b RideBuilder) WithSection(input string) RideBuilder {
}
func (b RideBuilder) Build() ride.Ride {
- points := []ride.Point{ride.NewPoint(0, 0)}
+ points := []ride.Point{{}}
distance := 0.0
elevation := 0.0
for _, section := range b.sections {
@@ -48,10 +49,11 @@ func (b RideBuilder) Build() ride.Ride {
for curSecDist < section.distance {
curSecDist += b.precision
elevation += b.precision * section.slope
- points = append(points, ride.NewPoint(
- distance+curSecDist,
- elevation,
- ))
+ points = append(points, ride.Point{
+ DistanceM: distance + curSecDist,
+ ElevationM: elevation,
+ Timestamp: points[len(points)-1].Timestamp.Add(time.Second),
+ })
}
distance += curSecDist
}
@@ -63,7 +65,7 @@ func TestClimbThenFalseFlat(t *testing.T) {
climbs := r.AllClimbs()
assert.Len(t, climbs, 1)
- assert.Equal(t, "0.0km-2.0km: 2.0km at 7.0% (98 pts - Cat 3)", r.String(climbs[0]))
+ assert.Equal(t, "0.0km-2.0km: 2.0km at 7.0% (98 pts - Cat 3)", climbs[0].String())
}
func TestSmallClimbWithDescentInsideFalseFlat(t *testing.T) {
@@ -71,7 +73,7 @@ func TestSmallClimbWithDescentInsideFalseFlat(t *testing.T) {
climbs := r.AllClimbs()
assert.Len(t, climbs, 1)
- assert.Equal(t, "10.0km-12.0km: 2.0km at 7.0% (98 pts - Cat 3)", r.String(climbs[0]))
+ assert.Equal(t, "10.0km-12.0km: 2.0km at 7.0% (98 pts - Cat 3)", climbs[0].String())
}
func TestPogacar20220721(t *testing.T) {
@@ -81,12 +83,12 @@ func TestPogacar20220721(t *testing.T) {
climbs := r.AllClimbs()
assert.Len(t, climbs, 6)
- assert.Equal(t, "0.6km-5.6km: 5.0km at 3.5% (62 pts - Cat 4)", r.String(climbs[0]))
- assert.Equal(t, "42.9km-45.2km: 2.3km at 5.0% (59 pts - Cat 4)", r.String(climbs[1]))
- assert.Equal(t, "60.0km-76.4km: 16.4km at 7.2% (854 pts - HC)", r.String(climbs[2]))
- assert.Equal(t, "83.8km-85.9km: 2.0km at 5.4% (59 pts - Cat 4)", r.String(climbs[3]))
- assert.Equal(t, "99.2km-109.3km: 10.2km at 8.4% (710 pts - HC)", r.String(climbs[4]))
- assert.Equal(t, "128.6km-142.2km: 13.6km at 7.8% (832 pts - HC)", r.String(climbs[5]))
+ assert.Equal(t, "0.6km-5.6km: 5.0km at 3.5% (62 pts - Cat 4)", climbs[0].String())
+ assert.Equal(t, "42.9km-45.2km: 2.3km at 5.0% (59 pts - Cat 4)", climbs[1].String())
+ assert.Equal(t, "60.0km-76.4km: 16.4km at 7.2% (854 pts - HC)", climbs[2].String())
+ assert.Equal(t, "83.8km-85.9km: 2.0km at 5.4% (59 pts - Cat 4)", climbs[3].String())
+ assert.Equal(t, "99.2km-109.3km: 10.2km at 8.4% (710 pts - HC)", climbs[4].String())
+ assert.Equal(t, "128.6km-142.2km: 13.6km at 7.8% (832 pts - HC)", climbs[5].String())
}
func TestBouclesVerdon2024(t *testing.T) {
@@ -96,12 +98,12 @@ func TestBouclesVerdon2024(t *testing.T) {
climbs := r.AllClimbs()
assert.Len(t, climbs, 6)
- assert.Equal(t, "3.6km-13.8km: 10.3km at 2.1% (44 pts - Cat 4)", r.String(climbs[0])) // TODO: Not steep enough
- assert.Equal(t, "34.7km-37.3km: 2.5km at 5.0% (62 pts - Cat 4)", r.String(climbs[1]))
- assert.Equal(t, "41.9km-51.0km: 9.0km at 2.5% (55 pts - Cat 4)", r.String(climbs[2])) // TODO: Not steep enough
- assert.Equal(t, "57.6km-60.8km: 3.1km at 6.3% (124 pts - Cat 3)", r.String(climbs[3]))
- assert.Equal(t, "71.7km-74.7km: 3.0km at 5.3% (84 pts - Cat 3)", r.String(climbs[4]))
- assert.Equal(t, "77.4km-78.7km: 1.3km at 5.3% (37 pts - Cat 4)", r.String(climbs[5]))
+ assert.Equal(t, "3.6km-13.8km: 10.3km at 2.1% (44 pts - Cat 4)", climbs[0].String()) // TODO: Not steep enough
+ assert.Equal(t, "34.7km-37.3km: 2.5km at 5.0% (62 pts - Cat 4)", climbs[1].String())
+ assert.Equal(t, "41.9km-51.0km: 9.0km at 2.5% (55 pts - Cat 4)", climbs[2].String()) // TODO: Not steep enough
+ assert.Equal(t, "57.6km-60.8km: 3.1km at 6.3% (124 pts - Cat 3)", climbs[3].String())
+ assert.Equal(t, "71.7km-74.7km: 3.0km at 5.3% (84 pts - Cat 3)", climbs[4].String())
+ assert.Equal(t, "77.4km-78.7km: 1.3km at 5.3% (37 pts - Cat 4)", climbs[5].String())
}
func TestMimetArbois20241229(t *testing.T) {
@@ -111,8 +113,8 @@ func TestMimetArbois20241229(t *testing.T) {
climbs := r.AllClimbs()
assert.Len(t, climbs, 2)
- assert.Equal(t, "3.9km-5.3km: 1.4km at 10.3% (153 pts - Cat 3)", r.String(climbs[0]))
- assert.Equal(t, "14.4km-19.8km: 5.4km at 4.3% (98 pts - Cat 3)", r.String(climbs[1]))
+ assert.Equal(t, "3.9km-5.3km: 1.4km at 10.3% (153 pts - Cat 3)", climbs[0].String())
+ assert.Equal(t, "14.4km-19.8km: 5.4km at 4.3% (98 pts - Cat 3)", climbs[1].String())
}
func TestMimetArbois20250104(t *testing.T) {
@@ -122,8 +124,8 @@ func TestMimetArbois20250104(t *testing.T) {
climbs := r.AllClimbs()
assert.Len(t, climbs, 2)
- assert.Equal(t, "4.5km-5.9km: 1.4km at 10.1% (148 pts - Cat 3)", r.String(climbs[0]))
- assert.Equal(t, "12.0km-20.2km: 8.2km at 2.9% (67 pts - Cat 4)", r.String(climbs[1])) // TODO: Not steep enough
+ assert.Equal(t, "4.5km-5.9km: 1.4km at 10.1% (148 pts - Cat 3)", climbs[0].String())
+ assert.Equal(t, "12.0km-20.2km: 8.2km at 2.9% (67 pts - Cat 4)", climbs[1].String()) // TODO: Not steep enough
}
// https://www.strava.com/activities/9282265844
@@ -134,10 +136,10 @@ func TestAlpesVerdonTour20230617(t *testing.T) {
climbs := r.AllClimbs()
assert.Len(t, climbs, 6)
- assert.Equal(t, "0.7km-18.6km: 17.9km at 3.5% (217 pts - Cat 2)", r.String(climbs[0])) // TODO: Should split in 2 (10km at 4.5% is Cat 2)
- assert.Equal(t, "36.1km-41.6km: 5.4km at 5.8% (182 pts - Cat 2)", r.String(climbs[1]))
- assert.Equal(t, "47.0km-48.2km: 1.2km at 6.1% (45 pts - Cat 4)", r.String(climbs[2]))
- assert.Equal(t, "51.9km-54.7km: 2.8km at 4.0% (45 pts - Cat 4)", r.String(climbs[3]))
- assert.Equal(t, "86.7km-92.8km: 6.1km at 2.7% (44 pts - Cat 4)", r.String(climbs[4]))
- assert.Equal(t, "114.1km-114.7km: 0.6km at 14.3% (118 pts - Cat 3)", r.String(climbs[5]))
+ assert.Equal(t, "0.7km-18.6km: 17.9km at 3.5% (217 pts - Cat 2)", climbs[0].String()) // TODO: Should split in 2 (10km at 4.5% is Cat 2)
+ assert.Equal(t, "36.1km-41.6km: 5.4km at 5.8% (182 pts - Cat 2)", climbs[1].String())
+ assert.Equal(t, "47.0km-48.2km: 1.2km at 6.1% (45 pts - Cat 4)", climbs[2].String())
+ assert.Equal(t, "51.9km-54.7km: 2.8km at 4.0% (45 pts - Cat 4)", climbs[3].String())
+ assert.Equal(t, "86.7km-92.8km: 6.1km at 2.7% (44 pts - Cat 4)", climbs[4].String())
+ assert.Equal(t, "114.1km-114.7km: 0.6km at 14.3% (118 pts - Cat 3)", climbs[5].String())
}
diff --git a/ride/ride.go b/ride/ride.go
index a745b0c..c8ecacc 100644
--- a/ride/ride.go
+++ b/ride/ride.go
@@ -1,8 +1,7 @@
package ride
import (
- "fmt"
-
+ "github.com/jftuga/geodist"
"github.com/martinlehoux/kagamigo/kcore"
"github.com/tkrajina/gpxgo/gpx"
)
@@ -25,7 +24,7 @@ func FromGPX(content *gpx.GPX) Ride {
}
kcore.Assert(i == 0 || distance > 0, "zero distance")
kcore.Assert(p.Elevation.NotNull(), "points without elevation")
- points[i] = Point{distance: distance, elevation: p.Elevation.Value()}
+ points[i] = Point{DistanceM: distance, ElevationM: p.Elevation.Value(), Coord: geodist.Coord{Lat: p.Latitude, Lon: p.Longitude}, Timestamp: p.Timestamp}
}
ride := Ride{points}
ride.check()
@@ -38,21 +37,14 @@ func FromPoints(points []Point) Ride {
return ride
}
-func (r *Ride) String(climb Climb) string {
- start := r.points[climb.start]
- end := r.points[climb.end]
- score := Score(r.points, climb.start, climb.end)
- return fmt.Sprintf("%.1fkm-%.1fkm: %.1fkm at %.1f%% (%d pts - %s)", start.distance/1000, end.distance/1000, (end.distance-start.distance)/1000, Slope(start, end)*100, int(score), Category(score))
-}
-
func (r *Ride) ScoreFromKm(start, end float64) float64 {
i := 0
j := 0
for k, p := range r.points {
- if i == 0 && p.distance >= start*1000 {
+ if i == 0 && p.DistanceM >= start*1000 {
i = k
}
- if j == 0 && p.distance >= end*1000 {
+ if j == 0 && p.DistanceM >= end*1000 {
j = k
break
}
@@ -63,13 +55,13 @@ func (r *Ride) ScoreFromKm(start, end float64) float64 {
func (r *Ride) ClimbFromDist(startDist, endDist float64) Climb {
start, end := 0, 0
for i, p := range r.points {
- if start == 0 && p.distance >= startDist {
+ if start == 0 && p.DistanceM >= startDist {
start = i
}
- if end == 0 && p.distance >= endDist {
+ if end == 0 && p.DistanceM >= endDist {
end = i
break
}
}
- return Climb{start, end}
+ return Climb{start: start, end: end, points: r.points[start : end+1]}
}