package rideanalysis import ( "slices" "github.com/martinlehoux/biking_home/climb" "github.com/martinlehoux/biking_home/core" "github.com/martinlehoux/biking_home/mountain_pass" "github.com/martinlehoux/biking_home/official_climb" "github.com/martinlehoux/biking_home/ride" ) const ( passMatchRadiusM = 300 passMatchElevationToleranceM = 50 passCrossingRadiusM = 100 passCrossingElevationToleranceM = 25 ) type Result struct { Climbs []AnalyzedClimb Crossings []mountain_pass.Crossing } type AnalyzedClimb struct { Segment climb.Climb Name string Category string DistanceM float64 SlopePercent float64 Cotacol float64 OfficialClimbID int64 OfficialName string Interval core.Interval } 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), } claimedClimbs := make([]AnalyzedClimb, 0) claimedRanges := make([]core.Interval, 0) for _, official := range officialClimbs { for _, matched := range official_climb.MatchClimbs(parsed.Coords, official, matchPolicy.EndpointRadiusM) { interval := core.NewInterval(matched.StartIndex, matched.EndIndex) claimedRanges = append(claimedRanges, interval) claimedClimbs = append(claimedClimbs, analyzedClimb(routeClimb(parsed.Route, interval), interval, official.Name, official.ID)) } } slices.SortFunc(claimedRanges, func(a, b core.Interval) int { return a.Start - b.Start }) for _, detected := range climb.Detect(parsed.Route, claimedRanges, climb.ClimbSlopeMinimum) { name := "" if matchedPass, ok := mountain_pass.MatchClimb(detected.Climb, passes, passMatchRadiusM, passMatchElevationToleranceM); ok { name = matchedPass.Name } interval := core.NewInterval(detected.Start, detected.Start+len(detected.Climb.Distances)-1) result.Climbs = append(result.Climbs, analyzedClimb(detected.Climb, interval, name, 0)) } climbs := make([]AnalyzedClimb, 0, len(claimedClimbs)+len(result.Climbs)) climbs = append(climbs, claimedClimbs...) climbs = append(climbs, result.Climbs...) slices.SortFunc(climbs, func(a, b AnalyzedClimb) int { return a.Interval.Start - b.Interval.Start }) result.Climbs = climbs for _, crossing := range mountain_pass.DetectCrossings(parsed.Coords, parsed.Distances, parsed.Elevations, passes, passCrossingRadiusM, passCrossingElevationToleranceM) { result.Crossings = append(result.Crossings, crossing) } return result } // routeClimb returns the climb segment of route covered by interval. func routeClimb(route climb.Route, interval core.Interval) climb.Climb { return climb.Climb{ Distances: route.Distances[interval.Start : interval.End+1], Elevations: route.Elevations[interval.Start : interval.End+1], Coords: route.Coords[interval.Start : interval.End+1], } } func analyzedClimb(segment climb.Climb, interval core.Interval, name string, officialID int64) AnalyzedClimb { cotacol := segment.DifficultyScore() return AnalyzedClimb{ Segment: segment, Name: name, Category: climb.CategoryOf(cotacol).String(), DistanceM: segment.Distances[len(segment.Distances)-1] - segment.Distances[0], SlopePercent: climb.Slope(segment.Distances, segment.Elevations) * 100, Cotacol: cotacol, OfficialClimbID: officialID, OfficialName: officialClimbNameForID(name, officialID), Interval: interval, } } func officialClimbNameForID(name string, officialID int64) string { if officialID == 0 { return "" } return name }