diff options
| author | Martin Kagamino Lehoux <martin@lehoux.net> | 2026-08-21 15:30:51 +0200 |
|---|---|---|
| committer | Martin Kagamino Lehoux <martin@lehoux.net> | 2026-08-21 15:30:51 +0200 |
| commit | 5e3d39cda5250cb55bc8324bcf96b0ab4b36c47f (patch) | |
| tree | f92fe6f938866688b994c99670e1e27863579255 /ride/climb.go | |
| parent | 0e27097e3d706e0e5bd761bf35fe301fd119d2a9 (diff) | |
feat: bound climb detection by matched official climbs
Detected climbs no longer absorb an official climb's range: official climbs
matched to the route by coordinates claim their stored start/end range, and
climb detection now runs only in the free regions around them. On ride 263 a
single absorbed Cat 2 ascent is split so creating a first-half official climb
lets the second half be detected on its own.
- ride: add Range, freeRegions, and DetectClimbs(claimed); exclude claimed
ranges so a climb never overlaps one
- official_climb: MatchClimb now bounds-matches purely by coordinates without
needing a detected climb; extract geo.Nearest shared helper
- rideanalysis: Analyze builds claimed climbs from matched official climbs and
detects only in free regions; render official vs unmatched climbs separately
Diffstat (limited to 'ride/climb.go')
| -rw-r--r-- | ride/climb.go | 43 |
1 files changed, 41 insertions, 2 deletions
diff --git a/ride/climb.go b/ride/climb.go index 29a4dcf..88b9d3b 100644 --- a/ride/climb.go +++ b/ride/climb.go @@ -221,8 +221,47 @@ func climbsBetween(r Ride, start, end int) []Climb { return climbs } -func (ride *Ride) AllClimbs() []Climb { - climbs := climbsBetween(*ride, 0, ride.Len()-1) +type Range struct { + Start int + End int +} + +func isValidClaim(claim Range, routeLen int) bool { + return claim.Start >= 0 && claim.End < routeLen && claim.Start < claim.End +} + +// freeRegions returns the maximal sub-ranges of [rStart, rEnd] that are not +// covered by any claimed range, after discarding claims that fall outside. +func freeRegions(rStart, rEnd int, claimed []Range) []Range { + regions := make([]Range, 0, len(claimed)+1) + current := rStart + for _, claim := range claimed { + if !isValidClaim(claim, rEnd+1) { + continue + } + if claim.End < current { + continue + } + if claim.Start > current { + regions = append(regions, Range{Start: current, End: claim.Start - 1}) + } + if claim.End+1 > current { + current = claim.End + 1 + } + } + if current <= rEnd { + regions = append(regions, Range{Start: current, End: rEnd}) + } + return regions +} + +// returns the detected climbs of the whole ride, but never a climb that overlaps +// a claimed range. +func (ride *Ride) DetectClimbs(claimed []Range) []Climb { + climbs := make([]Climb, 0) + for _, region := range freeRegions(0, ride.Len()-1, claimed) { + climbs = append(climbs, climbsBetween(*ride, region.Start, region.End)...) + } slices.SortFunc(climbs, climbCmpStart) return climbs } |
