From 522512197de372aa9b9c5584711619a76a5546d8 Mon Sep 17 00:00:00 2001 From: Martin Kagamino Lehoux Date: Thu, 27 Aug 2026 12:07:11 +0200 Subject: clenaup --- climb/detection.go | 44 ++++++++++++++++++++++---------------------- climb/detection_test.go | 12 ++++++------ 2 files changed, 28 insertions(+), 28 deletions(-) (limited to 'climb') diff --git a/climb/detection.go b/climb/detection.go index 9269c2f..a7b92e9 100644 --- a/climb/detection.go +++ b/climb/detection.go @@ -5,7 +5,7 @@ import ( "math" "slices" - "github.com/martinlehoux/biking_home/core" + "github.com/martinlehoux/biking_home/geo" "github.com/martinlehoux/kagamigo/kcore" ) @@ -15,13 +15,13 @@ type DetectedClimb struct { } // returns the detected climbs but never a climb that overlaps a claimed range. -func Detect(route Route, claimed []core.Interval, minSlope float64) []DetectedClimb { +func Detect(route Route, claimed []geo.Interval, minSlope float64) []DetectedClimb { distances, elevations, coords := route.Distances, route.Elevations, route.Coords - bounds := make([]core.Interval, 0) + bounds := make([]geo.Interval, 0) for _, region := range freeRegions(0, len(distances)-1, claimed) { - bounds = append(bounds, climbsBetween(distances, elevations, core.NewInterval(region.Start, region.End), minSlope)...) + bounds = append(bounds, climbsBetween(distances, elevations, geo.NewInterval(region.Start, region.End), minSlope)...) } - slices.SortFunc(bounds, func(a, b core.Interval) int { return a.Start - b.Start }) + slices.SortFunc(bounds, func(a, b geo.Interval) int { return a.Start - b.Start }) climbs := make([]DetectedClimb, 0, len(bounds)) for _, b := range bounds { climbs = append(climbs, DetectedClimb{ @@ -38,8 +38,8 @@ func Detect(route Route, claimed []core.Interval, minSlope float64) []DetectedCl // 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 []core.Interval) []core.Interval { - valid := make([]core.Interval, 0, len(claimed)) +func freeRegions(rStart, rEnd int, claimed []geo.Interval) []geo.Interval { + valid := make([]geo.Interval, 0, len(claimed)) for _, claim := range claimed { // Discard claims outside the route or with a degenerate range. if claim.Start < 0 || claim.End >= rEnd+1 || claim.Start >= claim.End { @@ -47,31 +47,31 @@ func freeRegions(rStart, rEnd int, claimed []core.Interval) []core.Interval { } valid = append(valid, claim) } - slices.SortFunc(valid, func(a, b core.Interval) int { return a.Start - b.Start }) - regions := make([]core.Interval, 0, len(valid)+1) + slices.SortFunc(valid, func(a, b geo.Interval) int { return a.Start - b.Start }) + regions := make([]geo.Interval, 0, len(valid)+1) current := rStart for _, claim := range valid { if claim.End < current { continue } if claim.Start > current { - regions = append(regions, core.NewInterval(current, claim.Start-1)) + regions = append(regions, geo.NewInterval(current, claim.Start-1)) } if claim.End+1 > current { current = claim.End + 1 } } if current <= rEnd { - regions = append(regions, core.NewInterval(current, rEnd)) + regions = append(regions, geo.NewInterval(current, rEnd)) } return regions } // climbsBetween returns the detected climb bounds inside bounds from the // distance and elevation components. -func climbsBetween(distances, elevations []float64, bounds core.Interval, minSlope float64) []core.Interval { +func climbsBetween(distances, elevations []float64, bounds geo.Interval, minSlope float64) []geo.Interval { start, end := bounds.Start, bounds.End - climbs := []core.Interval{} + climbs := []geo.Interval{} if distances[end]-distances[start] < ClimbDistanceMinimum { return climbs } @@ -84,12 +84,12 @@ func climbsBetween(distances, elevations []float64, bounds core.Interval, minSlo } // TODO: Use descent to reduce recursion if distances[highest]-distances[start] < ClimbDistanceMinimum { - return climbsBetween(distances, elevations, core.NewInterval(start+1, end), minSlope) + return climbsBetween(distances, elevations, geo.NewInterval(start+1, end), minSlope) } climb, found := bestClimbBetween(distances[start:highest+1], elevations[start:highest+1], minSlope) if !found { // No qualifying climb up to the top; only the part past it can hold climbs. - return climbsBetween(distances, elevations, core.NewInterval(highest, end), minSlope) + return climbsBetween(distances, elevations, geo.NewInterval(highest, end), minSlope) } // bestClimbBetween returns an interval relative to its sliced input; // re-anchor it into this slice's index space. @@ -97,10 +97,10 @@ func climbsBetween(distances, elevations []float64, bounds core.Interval, minSlo climbEnd := start + climb.End if climbDetectionScore(distances[climbStart:climbEnd+1], elevations[climbStart:climbEnd+1]) >= detectionMinimumScore && distances[climbEnd]-distances[climbStart] >= ClimbDistanceMinimum { slog.Debug("Found climb between", slog.Int("start", int(distances[climbStart])), slog.Int("end", int(distances[climbEnd]))) - climbs = append(climbs, core.NewInterval(climbStart, climbEnd)) + climbs = append(climbs, geo.NewInterval(climbStart, climbEnd)) } - climbs = append(climbs, climbsBetween(distances, elevations, core.NewInterval(start, climbStart), minSlope)...) - climbs = append(climbs, climbsBetween(distances, elevations, core.NewInterval(climbEnd, end), minSlope)...) + climbs = append(climbs, climbsBetween(distances, elevations, geo.NewInterval(start, climbStart), minSlope)...) + climbs = append(climbs, climbsBetween(distances, elevations, geo.NewInterval(climbEnd, end), minSlope)...) return climbs } @@ -121,12 +121,12 @@ func climbDetectionScore(distances, elevations []float64) float64 { // returns the best qualifying sub-segment (found=false when none exists). The // returned interval is relative to the start of the given (already sliced) // slices; callers add back their offset to recover absolute indices. -func bestClimbBetween(distances, elevations []float64, minSlope float64) (core.Interval, bool) { +func bestClimbBetween(distances, elevations []float64, minSlope float64) (geo.Interval, bool) { end := len(distances) - 1 kcore.Assert(end > 0, "empty points") bestDetectionScore := climbDetectionScore(distances, elevations) - best := core.NewInterval(0, end) + best := geo.NewInterval(0, end) for i := range end { detectionScore := climbDetectionScore(distances[i:end], elevations[i:end]) if detectionScore > bestDetectionScore { @@ -154,7 +154,7 @@ func bestClimbBetween(distances, elevations []float64, minSlope float64) (core.I } // Classic best too gentle: re-segment into the best qualifying sub-segment. bestDetectionScore = math.Inf(-1) - best = core.NewInterval(-1, -1) + best = geo.NewInterval(-1, -1) for i := range end { for j := i + 1; j <= end; j++ { if Slope(distances[i:j+1], elevations[i:j+1]) < minSlope { @@ -169,7 +169,7 @@ func bestClimbBetween(distances, elevations []float64, minSlope float64) (core.I } } if best.Start < 0 { - return core.Interval{}, false + return geo.Interval{}, false } return best, true } diff --git a/climb/detection_test.go b/climb/detection_test.go index 3c6a33b..e02c02b 100644 --- a/climb/detection_test.go +++ b/climb/detection_test.go @@ -5,7 +5,7 @@ import ( "testing" "github.com/martinlehoux/biking_home/climb" - "github.com/martinlehoux/biking_home/core" + "github.com/martinlehoux/biking_home/geo" "github.com/martinlehoux/biking_home/internal/routetest" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -43,7 +43,7 @@ func TestDetectClimbsSkipsMiddleClaim(t *testing.T) { // A climb, a false flat giving a saddle, then a climb again. Claiming the // saddle's descent base should leave both climbs detected separately. route := routetest.MustRoute("2km at 7% | 1km at -7% | 2km at 7%") - detected := climb.Detect(route, []core.Interval{core.NewInterval(20, 20)}, climb.ClimbSlopeMinimum) + detected := climb.Detect(route, []geo.Interval{geo.NewInterval(20, 20)}, climb.ClimbSlopeMinimum) // No free region falls under the min distance except the two climbs. assert.Len(t, detected, 2) @@ -54,13 +54,13 @@ func TestDetectClimbsSkipsMiddleClaim(t *testing.T) { func TestDetectClimbsOverlappingClaimDropsClimb(t *testing.T) { // Claiming the ascent region entirely swallows the climb. route := routetest.MustRoute("2km at 7% | 20km at 0%") - detected := climb.Detect(route, []core.Interval{core.NewInterval(0, 40)}, climb.ClimbSlopeMinimum) + detected := climb.Detect(route, []geo.Interval{geo.NewInterval(0, 40)}, climb.ClimbSlopeMinimum) assert.Empty(t, detected) } func TestDetectClimbsIgnoresOutOfRangeClaim(t *testing.T) { route := routetest.MustRoute("2km at 7% | 20km at 0%") - detected := climb.Detect(route, []core.Interval{core.NewInterval(-5, -1)}, climb.ClimbSlopeMinimum) + detected := climb.Detect(route, []geo.Interval{geo.NewInterval(-5, -1)}, climb.ClimbSlopeMinimum) require.Len(t, detected, 1) assert.Equal(t, "0.0km-2.0km: 2.0km at 7.0% (98 pts - Cat 3)", describe(route, detected[0])) } @@ -74,10 +74,10 @@ func TestDetectClimbsWithUnsortedClaimsDropsOverlappingClimb(t *testing.T) { route := routetest.MustRoute("2km at 7% | 1km at -7% | 2km at 7%") // Both climbs claimed, but in reverse order (mimics ORDER BY id returning 30-50 before 0-20). // Sorted input correctly yields 0 detected climbs. - sorted := []core.Interval{core.NewInterval(0, 20), core.NewInterval(30, 50)} + sorted := []geo.Interval{geo.NewInterval(0, 20), geo.NewInterval(30, 50)} assert.Empty(t, climb.Detect(route, sorted, climb.ClimbSlopeMinimum)) - unsorted := []core.Interval{core.NewInterval(30, 50), core.NewInterval(0, 20)} + unsorted := []geo.Interval{geo.NewInterval(30, 50), geo.NewInterval(0, 20)} detected := climb.Detect(route, unsorted, climb.ClimbSlopeMinimum) // Before fix this fails: freeRegions skips {0,20} because 20 < 30 and returns 1 climb. assert.Empty(t, detected, "unsorted claimed ranges must still suppress overlapping climbs — expected 0, got %v", detected) -- cgit v1.3