From 20b4d461e7ed363868ce55d9894f62b14180309f Mon Sep 17 00:00:00 2001 From: Jo Vandeginste Date: Sun, 12 Jan 2025 22:08:02 +0100 Subject: [PATCH] Add route segment tests This is only the beginning... Signed-off-by: Jo Vandeginste --- pkg/database/route_segment_matching.go | 41 +- pkg/database/route_segment_test.go | 108 ++++ pkg/database/segment1_test.go | 77 +++ pkg/database/segment2_test.go | 75 +++ pkg/database/track_test.go | 654 +++++++++++++++++++++++++ views/route_segments/add.templ | 6 +- views/route_segments/add_templ.go | 2 +- views/route_segments/list.templ | 5 + views/route_segments/list_templ.go | 37 +- 9 files changed, 981 insertions(+), 24 deletions(-) create mode 100644 pkg/database/route_segment_test.go create mode 100644 pkg/database/segment1_test.go create mode 100644 pkg/database/segment2_test.go create mode 100644 pkg/database/track_test.go diff --git a/pkg/database/route_segment_matching.go b/pkg/database/route_segment_matching.go index b1997299..389b6634 100644 --- a/pkg/database/route_segment_matching.go +++ b/pkg/database/route_segment_matching.go @@ -7,26 +7,26 @@ import ( // MaxDeltaMeter is the maximum distance in meters that a point can be away from // the route segment -const MaxDeltaMeter = 20 +const MaxDeltaMeter = 20.0 // MaxTotalDistancePercentage is the maximum percentage of the total distance of // the route segment that can be exceeded by the total distance matching part of // the route -const MaxTotalDistancePercentage = 0.02 +const MaxTotalDistanceFraction = 0.9 // RouteSegmentMatch is a match between a route segment and a workout type RouteSegmentMatch struct { Workout *Workout RouteSegment *RouteSegment - first, last, end MapPoint // The first and last point of the route + first, last MapPoint // The first and last point of the route + end MapPoint // The last point of the workout RouteSegmentID uint `gorm:"primaryKey"` // The ID of the route segment WorkoutID uint `gorm:"primaryKey"` // The ID of the workout FirstID, LastID int // The index of the first and last point of the route Distance float64 // The total distance of the route segment for this workout Duration time.Duration // The total duration of the route segment for this workout - Points int // The total number of points of the route segment for this workout } func (rsm *RouteSegmentMatch) AverageSpeed() float64 { @@ -37,14 +37,10 @@ func (rsm *RouteSegmentMatch) AverageSpeed() float64 { // the first and last point of the route along the route segment func (rs *RouteSegment) NewRouteSegmentMatch(workout *Workout, p, last int) *RouteSegmentMatch { rsm := &RouteSegmentMatch{ - RouteSegmentID: rs.ID, - WorkoutID: workout.ID, - FirstID: p, - LastID: last, - - first: workout.Data.Details.Points[p], - last: workout.Data.Details.Points[last], - end: workout.Data.Details.Points[len(workout.Data.Details.Points)-1], + Workout: workout, + RouteSegment: rs, + FirstID: p, + LastID: last, } rsm.calculate() @@ -62,13 +58,19 @@ func (rsm *RouteSegmentMatch) IsBetterThan(current *RouteSegmentMatch) bool { // within MaxTotalDistancePercentage of the distance of the current route // segment func (rsm *RouteSegmentMatch) MatchesDistance(distance float64) bool { - return math.Abs(1-(rsm.Distance/distance)) < MaxTotalDistancePercentage + return math.Abs(rsm.Distance/distance) > MaxTotalDistanceFraction } // calculate will calculate the total distance and duration of the route // segment, and the total number of points of this workout along the route // segment func (rsm *RouteSegmentMatch) calculate() { + rsm.RouteSegmentID = rsm.RouteSegment.ID + rsm.WorkoutID = rsm.Workout.ID + rsm.first = rsm.Workout.Data.Details.Points[rsm.FirstID] + rsm.last = rsm.Workout.Data.Details.Points[rsm.LastID] + rsm.end = rsm.Workout.Data.Details.Points[len(rsm.Workout.Data.Details.Points)-1] + if rsm.FirstID <= rsm.LastID { rsm.Distance = rsm.last.TotalDistance - rsm.first.TotalDistance rsm.Duration = rsm.last.TotalDuration - rsm.first.TotalDuration @@ -166,12 +168,16 @@ func (rs *RouteSegment) MatchSegment(workout *Workout, start int, forward bool) if forward { cur++ + + if cur == segmentLength { + return index, true + } } else { cur-- - } - if cur%segmentLength == 0 { - return index, true + if cur == 0 { + return index, true + } } if !rs.Circular && index < start { @@ -190,7 +196,8 @@ func (rs *RouteSegment) StartingPoints(points []MapPoint) []int { start := rs.Points[0] for i, p := range points { - if start.DistanceTo(&p) < MaxDeltaMeter { + d := start.DistanceTo(&p) + if d < MaxDeltaMeter { r = append(r, i) } } diff --git a/pkg/database/route_segment_test.go b/pkg/database/route_segment_test.go new file mode 100644 index 00000000..c6c73b59 --- /dev/null +++ b/pkg/database/route_segment_test.go @@ -0,0 +1,108 @@ +package database + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestRouteSegment_Parse(t *testing.T) { + { + rs, err := NewRouteSegment("", "meer.gpx", []byte(meer)) + assert.NoError(t, err) + assert.NotNil(t, rs) + assert.Greater(t, rs.TotalDistance, 1800.0) + } + + { + rs, err := NewRouteSegment("", "finsepiste.gpx", []byte(finsepiste)) + assert.NoError(t, err) + assert.NotNil(t, rs) + assert.Greater(t, rs.TotalDistance, 900.0) + } +} + +func TestRouteSegment_FindMatches(t *testing.T) { + rs, err := NewRouteSegment("", "finsepiste.gpx", []byte(finsepiste)) + assert.NoError(t, err) + + w1, err := NewWorkout(AnonymousUser(), WorkoutTypeAutoDetect, "", "match.gpx", []byte(track)) + assert.NoError(t, err) + assert.True(t, w1.Type.IsLocation()) + assert.True(t, w1.HasTracks()) + + w2, err := NewWorkout(AnonymousUser(), WorkoutTypeAutoDetect, "", "nomatch.gpx", []byte(GpxSample1)) + assert.NoError(t, err) + assert.True(t, w2.Type.IsLocation()) + assert.True(t, w2.HasTracks()) + + workouts := []*Workout{w1, w2} + matches := rs.FindMatches(workouts) + + assert.Len(t, matches, 1) + assert.Len(t, matches[0].Workout.Data.Details.Points, 158) +} + +func TestRouteSegment_StartingPoints_NoMatch(t *testing.T) { + rs, err := NewRouteSegment("", "finsepiste.gpx", []byte(finsepiste)) + assert.NoError(t, err) + + w, err := NewWorkout(AnonymousUser(), WorkoutTypeAutoDetect, "", "nomatch.gpx", []byte(GpxSample1)) + assert.NoError(t, err) + + sp := rs.StartingPoints(w.Data.Details.Points) + assert.Empty(t, sp) +} + +func TestRouteSegment_StartingPoints_Match(t *testing.T) { + rs, err := NewRouteSegment("", "finsepiste.gpx", []byte(finsepiste)) + assert.NoError(t, err) + + w, err := NewWorkout(AnonymousUser(), WorkoutTypeAutoDetect, "", "match.gpx", []byte(track)) + assert.NoError(t, err) + + sp := rs.StartingPoints(w.Data.Details.Points) + assert.NotEmpty(t, sp) + assert.Greater(t, len(sp), 0) + + for _, p := range sp { + assert.Less(t, rs.Points[0].DistanceTo(&w.Data.Details.Points[p]), MaxDeltaMeter) + } +} + +func TestRouteSegment_StartingPoints_MatchSegment(t *testing.T) { + rs, err := NewRouteSegment("", "finsepiste.gpx", []byte(finsepiste)) + assert.NoError(t, err) + + w, err := NewWorkout(AnonymousUser(), WorkoutTypeAutoDetect, "", "match.gpx", []byte(track)) + assert.NoError(t, err) + + sp := rs.StartingPoints(w.Data.Details.Points) + assert.NotEmpty(t, sp) + assert.Greater(t, len(sp), 0) + + { + last, ok := rs.MatchSegment(w, 3, true) + assert.Zero(t, last) + assert.False(t, ok) + } + + { + last, ok := rs.MatchSegment(w, 4, true) + assert.NotZero(t, last) + assert.True(t, ok) + } +} + +func TestRouteSegment_Match(t *testing.T) { + rs, err := NewRouteSegment("", "finsepiste.gpx", []byte(finsepiste)) + assert.NoError(t, err) + + w, err := NewWorkout(AnonymousUser(), WorkoutTypeAutoDetect, "", "match.gpx", []byte(track)) + assert.NoError(t, err) + + rsm := rs.Match(w) + assert.NotNil(t, rsm) + assert.Greater(t, rsm.Distance, 900.0) + assert.True(t, rsm.MatchesDistance(rs.TotalDistance)) +} diff --git a/pkg/database/segment1_test.go b/pkg/database/segment1_test.go new file mode 100644 index 00000000..b77c362f --- /dev/null +++ b/pkg/database/segment1_test.go @@ -0,0 +1,77 @@ +package database + +//nolint:lll +const meer = ` + + + + +Runnermaps route + + + + +Rotselaar Het meer + +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 + + + +` diff --git a/pkg/database/segment2_test.go b/pkg/database/segment2_test.go new file mode 100644 index 00000000..90f98a37 --- /dev/null +++ b/pkg/database/segment2_test.go @@ -0,0 +1,75 @@ +package database + +//nolint:lll +const finsepiste = ` + + + + route_86636_track + + gpx.studio + + + + Runnermaps route + + + + + finsepiste + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + +` diff --git a/pkg/database/track_test.go b/pkg/database/track_test.go new file mode 100644 index 00000000..0f721661 --- /dev/null +++ b/pkg/database/track_test.go @@ -0,0 +1,654 @@ +package database + +const track = ` + + + + 24 aug. 2024 15:17:28 + + gpx.studio + + + + + + 24 aug. 2024 15:17:28 + FitoTrack + running + + + 52.4583417160683 + + + + 52.438713463766405 + + + + 52.41191358060513 + + + + 52.43342956309676 + + + + 52.34359235257779 + + + + 52.37737585651892 + + + + 52.44022462884034 + + + + 52.444188099299325 + + + + 52.40361007457137 + + + + 52.39681555378454 + + + + 52.388510957837276 + + + + 52.391719663194415 + + + + 52.43890310871618 + + + + 52.504582931365455 + + + + 52.48193507369944 + + + + 52.51798341790284 + + + + 52.52062536823767 + + + + 52.53327054471646 + + + + 52.63443195654681 + + + + 52.64084882230435 + + + + 52.63782867198348 + + + + 52.627448335766964 + + + + 52.73710289858086 + + + + 52.74672846969553 + + + + 52.76522430186629 + + + + 52.81674887287634 + + + + 52.79900835076417 + + + + 52.74880475492154 + + + + 52.76314856159704 + + + + 52.770697847485714 + + + + 52.78938277964949 + + + + 52.80655763665284 + + + + 52.7907037548169 + + + + 52.84524793119107 + + + + 52.87544561970251 + + + + 52.90904002365061 + + + + 52.92111899006384 + + + + 52.955091049041236 + + + + 52.958865964463946 + + + + 52.91545688940814 + + + + 52.961507914798766 + + + + 53.01341014083811 + + + + 53.043418729356524 + + + + 53.09267791514754 + + + + 53.12683852916121 + + + + 53.12249740367293 + + + + 53.13363250499129 + + + + 53.067009362203905 + + + + 53.020203571711434 + + + + 53.05757289108225 + + + + 53.046814899836434 + + + + 53.01529623615759 + + + + 53.00869027040703 + + + + 53.02209021198767 + + + + 53.002272859692745 + + + + 52.94357665782332 + + + + 52.92734566591484 + + + + 52.899979572688 + + + + 52.84222723591345 + + + + 52.80617889171004 + + + + 52.800328236018075 + + + + 52.82486327891707 + + + + 52.82259843865479 + + + + 52.770319102542906 + + + + 52.71879507648962 + + + + 52.7104910254991 + + + + 52.691995193328346 + + + + 52.66576152023264 + + + + 52.635564376677955 + + + + 52.657457469242125 + + + + 52.676141856449156 + + + + 52.676896621551 + + + + 52.716719336220365 + + + + 52.722004326803514 + + + + 52.738424418705016 + + + + 52.80976579705323 + + + + 52.85090948689001 + + + + 52.91583399948069 + + + + 52.92904538602505 + + + + 52.989817328163724 + + + + 53.012465730786495 + + + + 53.07380388299073 + + + + 53.15099591703091 + + + + 53.176474825024776 + + + + 53.147410101601224 + + + + 53.13457637008616 + + + + 53.13608644524659 + + + + 53.129291924459764 + + + + 53.10211438626919 + + + + 53.073238217881915 + + + + 53.020015561631915 + + + + 52.99208271338275 + + + + 53.046060134734596 + + + + 53.03228253812466 + + + + 53.030017697862384 + + + + 52.98415577246478 + + + + 52.933952721578905 + + + + 52.910549553854295 + + + + 52.953958083953346 + + + + 52.94074669740898 + + + + 52.95414663898962 + + + + 52.95433519402589 + + + + 52.945842043042354 + + + + 52.891109311631915 + + + + 52.883182370713946 + + + + 52.817125437992125 + + + + 52.73974484891568 + + + + 52.690674218160936 + + + + 52.63461996662634 + + + + 52.59517436202952 + + + + 52.511754017268075 + + + + 52.40360952961462 + + + + 52.29810753725273 + + + + 52.26715508363945 + + + + 52.213931882432696 + + + + 52.16089778121897 + + + + 52.11409144576975 + + + + 52.02840626074603 + + + + 52.055206143907306 + + + + 52.10635305988806 + + + + 52.10956122028845 + + + + 52.07502295124548 + + + + 52.11918679140312 + + + + 52.165615471823045 + + + + 52.21204360728621 + + + + 52.3535939438515 + + + + 52.42701160742572 + + + + 52.480234808632474 + + + + 52.50967773204207 + + + + 52.57724419496758 + + + + 52.61574593446953 + + + + 52.66651519542098 + + + + 52.71067903557862 + + + + 52.71520871610318 + + + + 52.742197699257474 + + + + 52.84486918624827 + + + + 52.879407455291236 + + + + 52.90337628812466 + + + + 52.920928800157306 + + + + 52.886768186143634 + + + + 52.875632539868526 + + + + 52.9097931538822 + + + + 52.905829683423214 + + + + 52.925080825652564 + + + + 52.94678536318047 + + + + 53.04889064010569 + + + + 52.98623096777728 + + + + 52.981324177180184 + + + + 53.096640840649776 + + + + 53.0813531688794 + + + + 53.04889118506244 + + + + 53.037189873678514 + + + + 52.939048067212276 + + + + 52.93168733635987 + + + + 52.928667730995755 + + + + 52.88129573043772 + + + + 52.949616958465064 + + + + 52.96396076514056 + + + + + +` diff --git a/views/route_segments/add.templ b/views/route_segments/add.templ index c103b4d7..0a72cdd6 100644 --- a/views/route_segments/add.templ +++ b/views/route_segments/add.templ @@ -68,9 +68,9 @@ templ Add() {

{ i18n.T(ctx, "Resources") }

{ i18n.T(ctx, "You can find inspiration here:") }
diff --git a/views/route_segments/add_templ.go b/views/route_segments/add_templ.go index fa4d717b..a66b3b32 100644 --- a/views/route_segments/add_templ.go +++ b/views/route_segments/add_templ.go @@ -151,7 +151,7 @@ func Add() templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/views/route_segments/list.templ b/views/route_segments/list.templ index 1f7d09f9..3f81d843 100644 --- a/views/route_segments/list.templ +++ b/views/route_segments/list.templ @@ -41,6 +41,11 @@ templ List(segments []*database.RouteSegment) { @listDetails(s) + if s.Dirty { + + @helpers.IconFor("refresh") + + } @actions(s) diff --git a/views/route_segments/list_templ.go b/views/route_segments/list_templ.go index fe2f9ebf..88f14e4f 100644 --- a/views/route_segments/list_templ.go +++ b/views/route_segments/list_templ.go @@ -164,7 +164,38 @@ func List(segments []*database.RouteSegment) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if s.Dirty { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = helpers.IconFor("refresh").Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, " ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -172,12 +203,12 @@ func List(segments []*database.RouteSegment) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err }