aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--mountain_pass/detection.go30
-rw-r--r--mountain_pass/detection_test.go10
-rw-r--r--rideanalysis/analysis.go2
-rw-r--r--web/rides.go9
-rw-r--r--web/views.go18
5 files changed, 36 insertions, 33 deletions
diff --git a/mountain_pass/detection.go b/mountain_pass/detection.go
index 09792e3..139bc60 100644
--- a/mountain_pass/detection.go
+++ b/mountain_pass/detection.go
@@ -29,8 +29,8 @@ func LoadMountainPasses(db *sql.DB) ([]MountainPass, error) {
return loadMountainPasses(db, nil)
}
-func LoadMountainPassesAroundRide(db *sql.DB, route ride.Ride, marginM float64) ([]MountainPass, error) {
- return loadMountainPasses(db, rideBounds(route, marginM))
+func LoadMountainPassesAroundRide(db *sql.DB, routeCoords []geodist.Coord, marginM float64) ([]MountainPass, error) {
+ return loadMountainPasses(db, rideBounds(routeCoords, marginM))
}
type coordinateBounds struct {
@@ -40,11 +40,11 @@ type coordinateBounds struct {
maxLongitude float64
}
-func rideBounds(route ride.Ride, marginM float64) *coordinateBounds {
- minLatitude, maxLatitude := route.Coord(0).Lat, route.Coord(0).Lat
- minLongitude, maxLongitude := route.Coord(0).Lon, route.Coord(0).Lon
- for index := 1; index < route.Len(); index++ {
- coordinate := route.Coord(index)
+func rideBounds(coords []geodist.Coord, marginM float64) *coordinateBounds {
+ minLatitude, maxLatitude := coords[0].Lat, coords[0].Lat
+ minLongitude, maxLongitude := coords[0].Lon, coords[0].Lon
+ for index := 1; index < len(coords); index++ {
+ coordinate := coords[index]
minLatitude = math.Min(minLatitude, coordinate.Lat)
maxLatitude = math.Max(maxLatitude, coordinate.Lat)
minLongitude = math.Min(minLongitude, coordinate.Lon)
@@ -95,13 +95,13 @@ func loadMountainPasses(db *sql.DB, bounds *coordinateBounds) ([]MountainPass, e
return mountainPasses, rows.Err()
}
-func DetectCrossings(ride ride.Ride, passes []MountainPass, radiusM, elevationToleranceM float64) []Crossing {
+func DetectCrossings(coords []geodist.Coord, distances, elevations []float64, passes []MountainPass, radiusM, elevationToleranceM float64) []Crossing {
crossings := make([]Crossing, 0)
for _, mountainPass := range passes {
if mountainPass.Coord == nil {
continue
}
- crossing, found := nearestCrossing(ride, mountainPass)
+ crossing, found := nearestCrossing(coords, distances, elevations, mountainPass)
if found && crossing.DistanceToM <= radiusM && crossing.ElevationDiff <= elevationToleranceM {
crossings = append(crossings, crossing)
}
@@ -138,16 +138,16 @@ func MatchClimb(climb ride.Climb, passes []MountainPass, radiusM, elevationToler
return best, found
}
-func nearestCrossing(ride ride.Ride, mountainPass MountainPass) (Crossing, bool) {
+func nearestCrossing(coords []geodist.Coord, distances, elevations []float64, mountainPass MountainPass) (Crossing, bool) {
best := Crossing{Pass: mountainPass, DistanceToM: 1e18}
- for i := 0; i < ride.Len(); i++ {
- distanceKm, _ := geodist.HaversineDistance(ride.Coord(i), *mountainPass.Coord)
+ for i := 0; i < len(coords); i++ {
+ distanceKm, _ := geodist.HaversineDistance(coords[i], *mountainPass.Coord)
distanceM := distanceKm * 1000
if distanceM < best.DistanceToM {
best.DistanceToM = distanceM
- best.RideDistanceM = ride.DistanceM(i)
- best.RideElevation = ride.ElevationM(i)
- best.ElevationDiff = absFloat64(ride.ElevationM(i) - float64(mountainPass.Elevation))
+ best.RideDistanceM = distances[i]
+ best.RideElevation = elevations[i]
+ best.ElevationDiff = absFloat64(elevations[i] - float64(mountainPass.Elevation))
}
}
if best.DistanceToM > 1e17 {
diff --git a/mountain_pass/detection_test.go b/mountain_pass/detection_test.go
index f55532b..39bb9ae 100644
--- a/mountain_pass/detection_test.go
+++ b/mountain_pass/detection_test.go
@@ -18,7 +18,7 @@ func TestDetectCrossingsFindsPass(t *testing.T) {
Name: "Pas de Magnan", Elevation: 460,
Coord: &geodist.Coord{Lat: 43.62, Lon: 5.43},
}
- crossings := mountain_pass.DetectCrossings(r, []mountain_pass.MountainPass{pass}, 100, 25)
+ crossings := mountain_pass.DetectCrossings(r.Coords(), r.Distances(), r.Elevations(), []mountain_pass.MountainPass{pass}, 100, 25)
assert.Len(t, crossings, 1)
assert.Equal(t, "Pas de Magnan", crossings[0].Pass.Name)
assert.Less(t, crossings[0].DistanceToM, 10.0)
@@ -46,7 +46,7 @@ func TestLoadMountainPassesAroundRideUsesExpandedBounds(t *testing.T) {
[]geodist.Coord{{Lat: 43.0, Lon: 5.0}, {Lat: 43.1, Lon: 5.1}},
make([]time.Time, 2),
)
- passes, err := mountain_pass.LoadMountainPassesAroundRide(db, route, 10_000)
+ passes, err := mountain_pass.LoadMountainPassesAroundRide(db, route.Coords(), 10_000)
require.NoError(t, err)
require.Len(t, passes, 3)
assert.Equal(t, "On route edge", passes[0].Name)
@@ -81,13 +81,13 @@ func TestDetectCrossingsIgnoresDistantPass(t *testing.T) {
Name: "Col de la Faucille", Elevation: 1320,
Coord: &geodist.Coord{Lat: 46.37, Lon: 6.02},
}
- assert.Empty(t, mountain_pass.DetectCrossings(r, []mountain_pass.MountainPass{far}, 100, 25))
+ assert.Empty(t, mountain_pass.DetectCrossings(r.Coords(), r.Distances(), r.Elevations(), []mountain_pass.MountainPass{far}, 100, 25))
}
func TestDetectCrossingsIgnoresPassWithoutCoordinates(t *testing.T) {
r := rideFromPoint(t, 43.62, 5.43, 447)
noCoord := mountain_pass.MountainPass{Name: "Col de la Gatasse", Elevation: 120}
- assert.Empty(t, mountain_pass.DetectCrossings(r, []mountain_pass.MountainPass{noCoord}, 100, 25))
+ assert.Empty(t, mountain_pass.DetectCrossings(r.Coords(), r.Distances(), r.Elevations(), []mountain_pass.MountainPass{noCoord}, 100, 25))
}
func TestDetectCrossingsRequiresElevationAgreement(t *testing.T) {
@@ -96,7 +96,7 @@ func TestDetectCrossingsRequiresElevationAgreement(t *testing.T) {
Name: "Pas de Magnan", Elevation: 900,
Coord: &geodist.Coord{Lat: 43.62, Lon: 5.43},
}
- assert.Empty(t, mountain_pass.DetectCrossings(r, []mountain_pass.MountainPass{pass}, 100, 25))
+ assert.Empty(t, mountain_pass.DetectCrossings(r.Coords(), r.Distances(), r.Elevations(), []mountain_pass.MountainPass{pass}, 100, 25))
}
func rideFromPoint(t *testing.T, latitude, longitude, elevation float64) ride.Ride {
diff --git a/rideanalysis/analysis.go b/rideanalysis/analysis.go
index 55f77b8..5e08e65 100644
--- a/rideanalysis/analysis.go
+++ b/rideanalysis/analysis.go
@@ -59,7 +59,7 @@ func Analyze(parsed ride.Ride, passes []mountain_pass.MountainPass, officialClim
return a.Segment.StartIndex() - b.Segment.StartIndex()
})
result.Climbs = climbs
- for _, crossing := range mountain_pass.DetectCrossings(parsed, passes, passCrossingRadiusM, passCrossingElevationToleranceM) {
+ for _, crossing := range mountain_pass.DetectCrossings(parsed.Coords(), parsed.Distances(), parsed.Elevations(), passes, passCrossingRadiusM, passCrossingElevationToleranceM) {
result.Crossings = append(result.Crossings, crossing)
}
return result
diff --git a/web/rides.go b/web/rides.go
index 81017d9..71ab3fd 100644
--- a/web/rides.go
+++ b/web/rides.go
@@ -65,7 +65,7 @@ func (s *Server) handleRide(w http.ResponseWriter, r *http.Request) {
}), w)
return
}
- passes, err := mountain_pass.LoadMountainPassesAroundRide(s.db, parsed, rideDetailMountainPassMarginM)
+ passes, err := mountain_pass.LoadMountainPassesAroundRide(s.db, parsed.Coords(), rideDetailMountainPassMarginM)
if err != nil {
slog.Warn("Failed to load nearby mountain passes for ride detail", "ride_id", id, "error", err)
}
@@ -144,14 +144,15 @@ func (s *Server) handleCreateOfficialClimb(w http.ResponseWriter, r *http.Reques
http.Error(w, "the recorded route is unavailable", http.StatusBadRequest)
return
}
- if startIndex >= endIndex || endIndex >= parsed.Len() {
+ bounds := parsed.Coords()
+ if startIndex >= endIndex || endIndex >= len(bounds) {
http.Error(w, "the official climb boundaries must be ordered route points", http.StatusBadRequest)
return
}
created, err := official_climb.Create(s.db, official_climb.OfficialClimb{
Name: r.FormValue("name"),
- StartCoord: parsed.Coord(startIndex),
- EndCoord: parsed.Coord(endIndex),
+ StartCoord: bounds[startIndex],
+ EndCoord: bounds[endIndex],
})
if err != nil {
slog.Warn("Failed to create official climb", "ride_id", id, "error", err)
diff --git a/web/views.go b/web/views.go
index cfb31e5..8df860c 100644
--- a/web/views.go
+++ b/web/views.go
@@ -280,9 +280,9 @@ func officialClimbCreateURL(id int64) string {
}
func buildRideDetailView(item ride.StoredRide, parsed ride.Ride, passes []mountain_pass.MountainPass, officialClimbs []official_climb.OfficialClimb, matchPolicy official_climb.MatchPolicy) RideDetailView {
- coordinates := make([][]float64, parsed.Len())
- for i := 0; i < parsed.Len(); i++ {
- coordinate := parsed.Coord(i)
+ coords := parsed.Coords()
+ coordinates := make([][]float64, len(coords))
+ for i, coordinate := range coords {
coordinates[i] = []float64{coordinate.Lon, coordinate.Lat}
}
profile := buildRideProfile(parsed, passes, officialClimbs, matchPolicy)
@@ -321,16 +321,18 @@ func buildRideMapPasses(passes []mountain_pass.MountainPass) []RideMapPass {
}
func buildRideProfile(parsed ride.Ride, passes []mountain_pass.MountainPass, officialClimbs []official_climb.OfficialClimb, matchPolicy official_climb.MatchPolicy) RideProfile {
+ coords := parsed.Coords()
+ distances := parsed.Distances()
+ elevations := parsed.Elevations()
profile := RideProfile{
- Points: make([]RideProfilePoint, parsed.Len()),
+ Points: make([]RideProfilePoint, len(coords)),
Climbs: make([]RideProfileClimb, 0),
Crossings: make([]RideProfileCrossing, 0),
}
- for i := 0; i < parsed.Len(); i++ {
- coordinate := parsed.Coord(i)
+ for i, coordinate := range coords {
profile.Points[i] = RideProfilePoint{
- DistanceKm: parsed.DistanceM(i) / 1000,
- ElevationM: parsed.ElevationM(i),
+ DistanceKm: distances[i] / 1000,
+ ElevationM: elevations[i],
Latitude: coordinate.Lat,
Longitude: coordinate.Lon,
}