diff options
| -rw-r--r-- | README.md | 2 | ||||
| -rw-r--r-- | official_climb/matcher.go | 17 | ||||
| -rw-r--r-- | official_climb/official_climb_test.go | 48 | ||||
| -rw-r--r-- | rideanalysis/analysis.go | 2 |
4 files changed, 49 insertions, 20 deletions
@@ -131,7 +131,6 @@ TypeScript checks can also be run directly with `npm run check`, covering format ## TODO -- http://localhost:8080/rides/330: col de l'aire dei masco start before detected, and does not appear on ride - http://localhost:8080/rides/263: Cat 2 to split in 2. when drawing 1 off, try and create new detected climbs - Compute estimated power, speed - Plot speed and slope per segment, colored by heart rate @@ -145,6 +144,7 @@ TypeScript checks can also be run directly with `npm run check`, covering format - Destination via Start - Planning rides - Mountain pass from OSM +- Rides filters (duration slider, distance slider) ## Resources diff --git a/official_climb/matcher.go b/official_climb/matcher.go index 59c3e0e..e7b51de 100644 --- a/official_climb/matcher.go +++ b/official_climb/matcher.go @@ -7,7 +7,7 @@ import ( "github.com/martinlehoux/biking_home/ride" ) -func MatchClimb(climb ride.Climb, officialClimbs []OfficialClimb, policy MatchPolicy) (OfficialClimb, bool) { +func MatchClimb(route ride.Ride, climb ride.Climb, officialClimbs []OfficialClimb, policy MatchPolicy) (OfficialClimb, bool) { if policy.Validate() != nil { return OfficialClimb{}, false } @@ -15,14 +15,17 @@ func MatchClimb(climb ride.Climb, officialClimbs []OfficialClimb, policy MatchPo var best OfficialClimb found := false for _, official := range officialClimbs { - startIndex, startDistance := nearestClimbPoint(climb, official.StartCoord) - endIndex, endDistance := nearestClimbPoint(climb, official.EndCoord) + startIndex, startDistance := nearestRoutePoint(route, official.StartCoord) + endIndex, endDistance := nearestRoutePoint(route, official.EndCoord) if startDistance > policy.EndpointRadiusM || endDistance > policy.EndpointRadiusM { continue } if startIndex >= endIndex { continue } + if endIndex < climb.StartIndex() || startIndex > climb.EndIndex() { + continue + } totalDistance := startDistance + endDistance if totalDistance < bestDistance || (totalDistance == bestDistance && (!found || official.ID < best.ID)) { best = official @@ -33,11 +36,11 @@ func MatchClimb(climb ride.Climb, officialClimbs []OfficialClimb, policy MatchPo return best, found } -func nearestClimbPoint(climb ride.Climb, target geodist.Coord) (int, float64) { - bestIndex := climb.StartIndex() +func nearestRoutePoint(route ride.Ride, target geodist.Coord) (int, float64) { + bestIndex := 0 bestDistance := math.Inf(1) - for index := climb.StartIndex(); index <= climb.EndIndex(); index++ { - distance := distanceM(climb.PointCoord(index), target) + for index := 0; index < route.Len(); index++ { + distance := distanceM(route.Coord(index), target) if distance < bestDistance { bestIndex = index bestDistance = distance diff --git a/official_climb/official_climb_test.go b/official_climb/official_climb_test.go index 848216c..f8b0fe3 100644 --- a/official_climb/official_climb_test.go +++ b/official_climb/official_climb_test.go @@ -48,11 +48,11 @@ func TestCreateRejectsInvalidOfficialClimb(t *testing.T) { } func TestMatchClimbUsesOrderedCoordinates(t *testing.T) { - climb := testClimb( + parsed, climb := testClimb( geodist.Coord{Lat: 43.1002, Lon: 5.1002}, geodist.Coord{Lat: 43.2002, Lon: 5.2002}, ) - match, found := official_climb.MatchClimb(climb, []official_climb.OfficialClimb{ + match, found := official_climb.MatchClimb(parsed, climb, []official_climb.OfficialClimb{ {Name: "Same direction", StartCoord: geodist.Coord{Lat: 43.1, Lon: 5.1}, EndCoord: geodist.Coord{Lat: 43.2, Lon: 5.2}}, {Name: "Reverse direction", StartCoord: geodist.Coord{Lat: 43.2, Lon: 5.2}, EndCoord: geodist.Coord{Lat: 43.1, Lon: 5.1}}, }, official_climb.DefaultMatchPolicy()) @@ -62,11 +62,11 @@ func TestMatchClimbUsesOrderedCoordinates(t *testing.T) { } func TestMatchClimbRejectsEndpointOutsideRadius(t *testing.T) { - climb := testClimb( + parsed, climb := testClimb( geodist.Coord{Lat: 43.1, Lon: 5.1}, geodist.Coord{Lat: 43.3, Lon: 5.3}, ) - _, found := official_climb.MatchClimb(climb, []official_climb.OfficialClimb{{ + _, found := official_climb.MatchClimb(parsed, climb, []official_climb.OfficialClimb{{ Name: "Partial match", StartCoord: geodist.Coord{Lat: 43.1, Lon: 5.1}, EndCoord: geodist.Coord{Lat: 43.2, Lon: 5.2}, @@ -87,7 +87,7 @@ func TestMatchClimbFindsOfficialEndpointsInsideDetectedClimb(t *testing.T) { []time.Time{time.Unix(0, 0), time.Unix(60, 0), time.Unix(120, 0), time.Unix(180, 0)}, ) climb := parsed.ClimbFromIndexes(0, 3) - matched, found := official_climb.MatchClimb(climb, []official_climb.OfficialClimb{{ + matched, found := official_climb.MatchClimb(parsed, climb, []official_climb.OfficialClimb{{ Name: "Corrected boundaries", StartCoord: parsed.Coord(1), EndCoord: parsed.Coord(2), @@ -97,6 +97,32 @@ func TestMatchClimbFindsOfficialEndpointsInsideDetectedClimb(t *testing.T) { assert.Equal(t, "Corrected boundaries", matched.Name) } +func TestMatchClimbAllowsOfficialStartBeforeDetectedClimb(t *testing.T) { + parsed := ride.FromColumns( + []float64{0, 1000, 2000, 3000, 4000}, + []float64{100, 120, 300, 500, 100}, + []geodist.Coord{ + {Lat: 43.100, Lon: 5.100}, + {Lat: 43.101, Lon: 5.101}, + {Lat: 43.102, Lon: 5.102}, + {Lat: 43.103, Lon: 5.103}, + {Lat: 43.104, Lon: 5.104}, + }, + []time.Time{time.Unix(0, 0), time.Unix(60, 0), time.Unix(120, 0), time.Unix(180, 0), time.Unix(240, 0)}, + ) + detected := parsed.ClimbFromIndexes(1, 4) + official := official_climb.OfficialClimb{ + Name: "Wider canonical climb", + StartCoord: parsed.Coord(0), + EndCoord: parsed.Coord(3), + } + + matched, found := official_climb.MatchClimb(parsed, detected, []official_climb.OfficialClimb{official}, official_climb.DefaultMatchPolicy()) + + require.True(t, found) + assert.Equal(t, official.Name, matched.Name) +} + func TestMatchClimbMatchesSameOfficialClimbAcrossRides(t *testing.T) { official := official_climb.OfficialClimb{ Name: "Shared official climb", @@ -127,8 +153,8 @@ func TestMatchClimbMatchesSameOfficialClimbAcrossRides(t *testing.T) { []time.Time{time.Unix(0, 0), time.Unix(45, 0), time.Unix(90, 0), time.Unix(135, 0), time.Unix(180, 0)}, ) - firstMatch, firstFound := official_climb.MatchClimb(firstRide.ClimbFromIndexes(0, 3), []official_climb.OfficialClimb{official}, official_climb.DefaultMatchPolicy()) - secondMatch, secondFound := official_climb.MatchClimb(secondRide.ClimbFromIndexes(1, 4), []official_climb.OfficialClimb{official}, official_climb.DefaultMatchPolicy()) + firstMatch, firstFound := official_climb.MatchClimb(firstRide, firstRide.ClimbFromIndexes(0, 3), []official_climb.OfficialClimb{official}, official_climb.DefaultMatchPolicy()) + secondMatch, secondFound := official_climb.MatchClimb(secondRide, secondRide.ClimbFromIndexes(1, 4), []official_climb.OfficialClimb{official}, official_climb.DefaultMatchPolicy()) require.True(t, firstFound) require.True(t, secondFound) @@ -137,11 +163,11 @@ func TestMatchClimbMatchesSameOfficialClimbAcrossRides(t *testing.T) { } func TestMatchClimbChoosesNearestCandidate(t *testing.T) { - climb := testClimb( + parsed, climb := testClimb( geodist.Coord{Lat: 43.1, Lon: 5.1}, geodist.Coord{Lat: 43.2, Lon: 5.2}, ) - match, found := official_climb.MatchClimb(climb, []official_climb.OfficialClimb{ + match, found := official_climb.MatchClimb(parsed, climb, []official_climb.OfficialClimb{ {ID: 2, Name: "Farther", StartCoord: geodist.Coord{Lat: 43.1005, Lon: 5.1005}, EndCoord: geodist.Coord{Lat: 43.2005, Lon: 5.2005}}, {ID: 1, Name: "Nearest", StartCoord: geodist.Coord{Lat: 43.1001, Lon: 5.1001}, EndCoord: geodist.Coord{Lat: 43.2001, Lon: 5.2001}}, }, official_climb.DefaultMatchPolicy()) @@ -154,12 +180,12 @@ func TestMatchPolicyRejectsInvalidRadius(t *testing.T) { assert.EqualError(t, (official_climb.MatchPolicy{}).Validate(), "official climb endpoint radius must be greater than zero") } -func testClimb(start, end geodist.Coord) ride.Climb { +func testClimb(start, end geodist.Coord) (ride.Ride, ride.Climb) { parsed := ride.FromColumns( []float64{0, 1000, 2000}, []float64{100, 150, 200}, []geodist.Coord{{}, start, end}, []time.Time{time.Unix(0, 0), time.Unix(60, 0), time.Unix(120, 0)}, ) - return parsed.ClimbFromDist(1000, 2000) + return parsed, parsed.ClimbFromDist(1000, 2000) } diff --git a/rideanalysis/analysis.go b/rideanalysis/analysis.go index 122a29f..632f729 100644 --- a/rideanalysis/analysis.go +++ b/rideanalysis/analysis.go @@ -35,7 +35,7 @@ func Analyze(parsed ride.Ride, passes []mountain_pass.MountainPass, officialClim if matchedPass, ok := mountain_pass.MatchClimb(displayClimb, passes, 300, 50); ok { displayClimb.Name = matchedPass.Name } - matchedOfficial, officialFound := official_climb.MatchClimb(detectedClimb, officialClimbs, matchPolicy) + matchedOfficial, officialFound := official_climb.MatchClimb(parsed, detectedClimb, officialClimbs, matchPolicy) if officialFound { startIndex := nearestCoordIndex(parsed, matchedOfficial.StartCoord) endIndex := nearestCoordIndex(parsed, matchedOfficial.EndCoord) |