initial version
This commit is contained in:
160
.gitignore
vendored
Normal file
160
.gitignore
vendored
Normal file
@@ -0,0 +1,160 @@
|
||||
# Created by https://www.toptal.com/developers/gitignore/api/goland+all,go,linux,windows
|
||||
# Edit at https://www.toptal.com/developers/gitignore?templates=goland+all,go,linux,windows
|
||||
|
||||
### Go ###
|
||||
# If you prefer the allow list template instead of the deny list, see community template:
|
||||
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
|
||||
#
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# app specific
|
||||
bicyclePlanner
|
||||
daily/
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
|
||||
# Go workspace file
|
||||
go.work
|
||||
|
||||
### GoLand+all ###
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
|
||||
# User-specific stuff
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/**/usage.statistics.xml
|
||||
.idea/**/dictionaries
|
||||
.idea/**/shelf
|
||||
|
||||
# AWS User-specific
|
||||
.idea/**/aws.xml
|
||||
|
||||
# Generated files
|
||||
.idea/**/contentModel.xml
|
||||
|
||||
# Sensitive or high-churn files
|
||||
.idea/**/dataSources/
|
||||
.idea/**/dataSources.ids
|
||||
.idea/**/dataSources.local.xml
|
||||
.idea/**/sqlDataSources.xml
|
||||
.idea/**/dynamic.xml
|
||||
.idea/**/uiDesigner.xml
|
||||
.idea/**/dbnavigator.xml
|
||||
|
||||
# Gradle
|
||||
.idea/**/gradle.xml
|
||||
.idea/**/libraries
|
||||
|
||||
# Gradle and Maven with auto-import
|
||||
# When using Gradle or Maven with auto-import, you should exclude module files,
|
||||
# since they will be recreated, and may cause churn. Uncomment if using
|
||||
# auto-import.
|
||||
# .idea/artifacts
|
||||
# .idea/compiler.xml
|
||||
# .idea/jarRepositories.xml
|
||||
# .idea/modules.xml
|
||||
# .idea/*.iml
|
||||
# .idea/modules
|
||||
# *.iml
|
||||
# *.ipr
|
||||
|
||||
# CMake
|
||||
cmake-build-*/
|
||||
|
||||
# Mongo Explorer plugin
|
||||
.idea/**/mongoSettings.xml
|
||||
|
||||
# File-based project format
|
||||
*.iws
|
||||
|
||||
# IntelliJ
|
||||
out/
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Cursive Clojure plugin
|
||||
.idea/replstate.xml
|
||||
|
||||
# SonarLint plugin
|
||||
.idea/sonarlint/
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
||||
|
||||
# Editor-based Rest Client
|
||||
.idea/httpRequests
|
||||
|
||||
# Android studio 3.1+ serialized cache file
|
||||
.idea/caches/build_file_checksums.ser
|
||||
|
||||
### GoLand+all Patch ###
|
||||
# Ignore everything but code style settings and run configurations
|
||||
# that are supposed to be shared within teams.
|
||||
|
||||
.idea/*
|
||||
|
||||
!.idea/codeStyles
|
||||
!.idea/runConfigurations
|
||||
|
||||
### Linux ###
|
||||
*~
|
||||
|
||||
# temporary files which can be created if a process still has a handle open of a deleted file
|
||||
.fuse_hidden*
|
||||
|
||||
# KDE directory preferences
|
||||
.directory
|
||||
|
||||
# Linux trash folder which might appear on any partition or disk
|
||||
.Trash-*
|
||||
|
||||
# .nfs files are created when an open file is removed but is still being accessed
|
||||
.nfs*
|
||||
|
||||
### Windows ###
|
||||
# Windows thumbnail cache files
|
||||
Thumbs.db
|
||||
Thumbs.db:encryptable
|
||||
ehthumbs.db
|
||||
ehthumbs_vista.db
|
||||
|
||||
# Dump file
|
||||
*.stackdump
|
||||
|
||||
# Folder config file
|
||||
[Dd]esktop.ini
|
||||
|
||||
# Recycle Bin used on file shares
|
||||
$RECYCLE.BIN/
|
||||
|
||||
# Windows Installer files
|
||||
*.cab
|
||||
*.msi
|
||||
*.msix
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
# Windows shortcuts
|
||||
*.lnk
|
||||
|
||||
# End of https://www.toptal.com/developers/gitignore/api/goland+all,go,linux,windows
|
17
go.mod
Normal file
17
go.mod
Normal file
@@ -0,0 +1,17 @@
|
||||
module bicyclePlanner
|
||||
|
||||
go 1.24
|
||||
|
||||
require github.com/tkrajina/gpxgo v1.4.0
|
||||
|
||||
require (
|
||||
github.com/fatih/color v1.18.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.9 // indirect
|
||||
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
||||
github.com/rs/zerolog v1.34.0 // indirect
|
||||
golang.org/x/net v0.0.0-20210614182718-04defd469f4e // indirect
|
||||
golang.org/x/sys v0.25.0 // indirect
|
||||
golang.org/x/text v0.3.6 // indirect
|
||||
)
|
45
go.sum
Normal file
45
go.sum
Normal file
@@ -0,0 +1,45 @@
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
||||
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
||||
github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
|
||||
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/tkrajina/gpxgo v1.4.0 h1:cSD5uSwy3VZuNFieTEZLyRnuIwhonQEkGPkPGW4XNag=
|
||||
github.com/tkrajina/gpxgo v1.4.0/go.mod h1:BXSMfUAvKiEhMEXAFM2NvNsbjsSvp394mOvdcNjettg=
|
||||
golang.org/x/net v0.0.0-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q=
|
||||
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
|
||||
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
592
main_test.go
Normal file
592
main_test.go
Normal file
@@ -0,0 +1,592 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/tkrajina/gpxgo/gpx"
|
||||
"math"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseMultiStops(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
expected []MultiStop
|
||||
}{
|
||||
{
|
||||
name: "Empty input",
|
||||
input: "",
|
||||
expected: []MultiStop{},
|
||||
},
|
||||
{
|
||||
name: "Single stop",
|
||||
input: "59.3293,18.0686:1",
|
||||
expected: []MultiStop{
|
||||
{Lat: 59.3293, Lon: 18.0686, Nights: 1},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Multiple stops",
|
||||
input: "59.3293,18.0686:1;60.1282,18.6435:2",
|
||||
expected: []MultiStop{
|
||||
{Lat: 59.3293, Lon: 18.0686, Nights: 1},
|
||||
{Lat: 60.1282, Lon: 18.6435, Nights: 2},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Invalid format",
|
||||
input: "invalid",
|
||||
expected: []MultiStop{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := parseMultiStops(tt.input)
|
||||
if len(result) != len(tt.expected) {
|
||||
t.Errorf("Expected %d stops, got %d", len(tt.expected), len(result))
|
||||
return
|
||||
}
|
||||
for i, stop := range result {
|
||||
if stop.Lat != tt.expected[i].Lat || stop.Lon != tt.expected[i].Lon || stop.Nights != tt.expected[i].Nights {
|
||||
t.Errorf("Stop %d mismatch: expected %+v, got %+v", i, tt.expected[i], stop)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHaversine(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
lat1 float64
|
||||
lon1 float64
|
||||
lat2 float64
|
||||
lon2 float64
|
||||
expected float64
|
||||
}{
|
||||
{
|
||||
name: "Same point",
|
||||
lat1: 59.3293,
|
||||
lon1: 18.0686,
|
||||
lat2: 59.3293,
|
||||
lon2: 18.0686,
|
||||
expected: 0,
|
||||
},
|
||||
{
|
||||
name: "Stockholm to Uppsala",
|
||||
lat1: 59.3293,
|
||||
lon1: 18.0686,
|
||||
lat2: 59.8586,
|
||||
lon2: 17.6389,
|
||||
expected: 63.63, // Approximate distance in km
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := haversine(tt.lat1, tt.lon1, tt.lat2, tt.lon2)
|
||||
// Allow for small floating point differences
|
||||
if math.Abs(result-tt.expected) > 0.5 {
|
||||
t.Errorf("Expected distance of %.2f km, got %.2f km", tt.expected, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeCutIndexes(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
cutIndexes []int
|
||||
days int
|
||||
totalPoints int
|
||||
expected []int
|
||||
}{
|
||||
{
|
||||
name: "No cuts",
|
||||
cutIndexes: []int{},
|
||||
days: 5,
|
||||
totalPoints: 100,
|
||||
expected: []int{},
|
||||
},
|
||||
{
|
||||
name: "Fewer cuts than days",
|
||||
cutIndexes: []int{25, 50, 75},
|
||||
days: 5,
|
||||
totalPoints: 100,
|
||||
expected: []int{25, 50, 75},
|
||||
},
|
||||
{
|
||||
name: "More cuts than days",
|
||||
cutIndexes: []int{20, 40, 60, 80, 99},
|
||||
days: 3,
|
||||
totalPoints: 100,
|
||||
expected: []int{20, 40, 99},
|
||||
},
|
||||
{
|
||||
name: "Duplicate cuts (rest days)",
|
||||
cutIndexes: []int{25, 25, 50, 75},
|
||||
days: 5,
|
||||
totalPoints: 100,
|
||||
expected: []int{25, 25, 50, 75},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := normalizeCutIndexes(tt.cutIndexes, tt.days, tt.totalPoints)
|
||||
if len(result) != len(tt.expected) {
|
||||
t.Errorf("Expected %d cuts, got %d", len(tt.expected), len(result))
|
||||
return
|
||||
}
|
||||
for i, cut := range result {
|
||||
if cut != tt.expected[i] {
|
||||
t.Errorf("Cut %d mismatch: expected %d, got %d", i, tt.expected[i], cut)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateSegment(t *testing.T) {
|
||||
// Create test points
|
||||
points := []TrackPoint{
|
||||
{Index: 0, Distance: 0, Elevation: 0},
|
||||
{Index: 1, Distance: 10, Elevation: 100},
|
||||
{Index: 2, Distance: 20, Elevation: 200},
|
||||
{Index: 3, Distance: 30, Elevation: 300},
|
||||
{Index: 4, Distance: 40, Elevation: 400},
|
||||
{Index: 5, Distance: 50, Elevation: 500},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
start int
|
||||
end int
|
||||
cutIndexes []int
|
||||
segmentIndex int
|
||||
expectedLen int
|
||||
}{
|
||||
{
|
||||
name: "Normal segment",
|
||||
start: 0,
|
||||
end: 3,
|
||||
cutIndexes: []int{3, 5},
|
||||
segmentIndex: 0,
|
||||
expectedLen: 3, // Points 0, 1, 2
|
||||
},
|
||||
{
|
||||
name: "Forced stop (end <= start)",
|
||||
start: 3,
|
||||
end: 3,
|
||||
cutIndexes: []int{3, 5},
|
||||
segmentIndex: 0,
|
||||
expectedLen: 2, // Points 3, 4 (halfway to the next cut)
|
||||
},
|
||||
{
|
||||
name: "Last segment",
|
||||
start: 3,
|
||||
end: 5,
|
||||
cutIndexes: []int{3, 5},
|
||||
segmentIndex: 1,
|
||||
expectedLen: 2, // Points 3, 4
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := createSegment(points, tt.start, tt.end, tt.cutIndexes, tt.segmentIndex)
|
||||
if len(result) != tt.expectedLen {
|
||||
t.Errorf("Expected segment length %d, got %d", tt.expectedLen, len(result))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateDaySegment(t *testing.T) {
|
||||
// Create test points
|
||||
points := []TrackPoint{
|
||||
{Index: 0, Distance: 0, Elevation: 0},
|
||||
{Index: 1, Distance: 10, Elevation: 100},
|
||||
{Index: 2, Distance: 20, Elevation: 200},
|
||||
}
|
||||
|
||||
dayNumber := 1
|
||||
elevFactor := 4.0
|
||||
forestRadius := 3
|
||||
resupplyRadius := 500
|
||||
|
||||
// This test will not query for forest or resupply points
|
||||
// since that would require network access. We're just testing the
|
||||
// calculation of distance, elevation, and effort.
|
||||
segment := createDaySegment(points, dayNumber, elevFactor, forestRadius, resupplyRadius, false)
|
||||
|
||||
if segment.DayNumber != dayNumber {
|
||||
t.Errorf("Expected day number %d, got %d", dayNumber, segment.DayNumber)
|
||||
}
|
||||
|
||||
expectedDist := 20.0 // Last point distance - first point distance
|
||||
if math.Abs(segment.Distance-expectedDist) > 0.1 {
|
||||
t.Errorf("Expected distance %.1f, got %.1f", expectedDist, segment.Distance)
|
||||
}
|
||||
|
||||
expectedElev := 200.0 // Last point elevation - first point elevation
|
||||
if math.Abs(segment.Elevation-expectedElev) > 0.1 {
|
||||
t.Errorf("Expected elevation %.1f, got %.1f", expectedElev, segment.Elevation)
|
||||
}
|
||||
|
||||
expectedEffort := expectedDist + (expectedElev / 100.0 * elevFactor)
|
||||
if math.Abs(segment.Effort-expectedEffort) > 0.1 {
|
||||
t.Errorf("Expected effort %.1f, got %.1f", expectedEffort, segment.Effort)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalculateCutIndexes(t *testing.T) {
|
||||
// Create test route data
|
||||
points := []TrackPoint{
|
||||
{Index: 0, Distance: 0, Elevation: 0},
|
||||
{Index: 1, Distance: 25, Elevation: 250}, // Effort: 25 + (250/100*4) = 35
|
||||
{Index: 2, Distance: 50, Elevation: 500}, // Effort: 50 + (500/100*4) = 70
|
||||
{Index: 3, Distance: 75, Elevation: 750}, // Effort: 75 + (750/100*4) = 105
|
||||
{Index: 4, Distance: 100, Elevation: 1000}, // Effort: 100 + (1000/100*4) = 140
|
||||
}
|
||||
|
||||
routeData := RouteData{
|
||||
Points: points,
|
||||
TotalDist: 100,
|
||||
TotalElev: 1000,
|
||||
TotalEffort: 140,
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
days int
|
||||
elevFactor float64
|
||||
multiStops []MultiStop
|
||||
expected []int
|
||||
}{
|
||||
{
|
||||
name: "2 days, no stops",
|
||||
days: 2,
|
||||
elevFactor: 4.0,
|
||||
multiStops: []MultiStop{},
|
||||
expected: []int{2, 4}, // Cut at index 2 (effort 70) and 4 (end)
|
||||
},
|
||||
{
|
||||
name: "3 days, no stops",
|
||||
days: 3,
|
||||
elevFactor: 4.0,
|
||||
multiStops: []MultiStop{},
|
||||
expected: []int{1, 3, 4}, // Cut at index 1, 3, and 4 (end)
|
||||
},
|
||||
{
|
||||
name: "3 days, with one multi-stop",
|
||||
days: 3,
|
||||
elevFactor: 4.0,
|
||||
multiStops: []MultiStop{
|
||||
{Lat: 0, Lon: 0, Nights: 1}, // This will match point 0
|
||||
},
|
||||
expected: []int{0, 1, 4}, // Cut at index 0 (forced stop), 1, and 4 (end)
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := calculateCutIndexes(routeData, tt.days, tt.elevFactor, tt.multiStops)
|
||||
if len(result) != len(tt.expected) {
|
||||
t.Errorf("Expected %d cuts, got %d", len(tt.expected), len(result))
|
||||
return
|
||||
}
|
||||
for i, cut := range result {
|
||||
if cut != tt.expected[i] {
|
||||
t.Errorf("Cut %d mismatch: expected %d, got %d", i, tt.expected[i], cut)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// New tests for refactored functions
|
||||
|
||||
func TestFindForcedStopIndexes(t *testing.T) {
|
||||
// Create test points with latitude and longitude
|
||||
points := []TrackPoint{
|
||||
{Index: 0, Point: gpx.GPXPoint{Point: gpx.Point{Latitude: 59.3293, Longitude: 18.0686}}},
|
||||
{Index: 1, Point: gpx.GPXPoint{Point: gpx.Point{Latitude: 59.8586, Longitude: 17.6389}}},
|
||||
{Index: 2, Point: gpx.GPXPoint{Point: gpx.Point{Latitude: 60.1282, Longitude: 18.6435}}},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
points []TrackPoint
|
||||
multiStops []MultiStop
|
||||
expected []int
|
||||
}{
|
||||
{
|
||||
name: "No stops",
|
||||
points: points,
|
||||
multiStops: []MultiStop{},
|
||||
expected: []int{},
|
||||
},
|
||||
{
|
||||
name: "One stop matching first point",
|
||||
points: points,
|
||||
multiStops: []MultiStop{
|
||||
{Lat: 59.3293, Lon: 18.0686, Nights: 1}, // Matches point 0
|
||||
},
|
||||
expected: []int{0},
|
||||
},
|
||||
{
|
||||
name: "One stop with multiple nights",
|
||||
points: points,
|
||||
multiStops: []MultiStop{
|
||||
{Lat: 59.8586, Lon: 17.6389, Nights: 2}, // Matches point 1
|
||||
},
|
||||
expected: []int{1, 1}, // Duplicated for 2 nights
|
||||
},
|
||||
{
|
||||
name: "Multiple stops",
|
||||
points: points,
|
||||
multiStops: []MultiStop{
|
||||
{Lat: 59.3293, Lon: 18.0686, Nights: 1}, // Matches point 0
|
||||
{Lat: 60.1282, Lon: 18.6435, Nights: 1}, // Matches point 2
|
||||
},
|
||||
expected: []int{0, 2},
|
||||
},
|
||||
{
|
||||
name: "Stop too far from any point",
|
||||
points: points,
|
||||
multiStops: []MultiStop{
|
||||
{Lat: 55.0, Lon: 15.0, Nights: 1}, // Far from all points
|
||||
},
|
||||
expected: []int{}, // No match within 2km
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := findForcedStopIndexes(tt.points, tt.multiStops)
|
||||
if len(result) != len(tt.expected) {
|
||||
t.Errorf("Expected %d forced stops, got %d", len(tt.expected), len(result))
|
||||
return
|
||||
}
|
||||
for i, idx := range result {
|
||||
if idx != tt.expected[i] {
|
||||
t.Errorf("Forced stop %d mismatch: expected %d, got %d", i, tt.expected[i], idx)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleTooManyForcedStops(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
forcedStopIndexes []int
|
||||
days int
|
||||
lastPointIndex int
|
||||
totalPoints int
|
||||
expected []int
|
||||
}{
|
||||
{
|
||||
name: "Fewer forced stops than days",
|
||||
forcedStopIndexes: []int{25, 50},
|
||||
days: 4,
|
||||
lastPointIndex: 99,
|
||||
totalPoints: 100,
|
||||
expected: []int{25, 50, 99},
|
||||
},
|
||||
{
|
||||
name: "More forced stops than days",
|
||||
forcedStopIndexes: []int{20, 40, 60, 80},
|
||||
days: 3,
|
||||
lastPointIndex: 99,
|
||||
totalPoints: 100,
|
||||
expected: []int{20, 40, 99}, // Only first (days-1) forced stops are kept
|
||||
},
|
||||
{
|
||||
name: "Equal forced stops and days",
|
||||
forcedStopIndexes: []int{33, 66},
|
||||
days: 3,
|
||||
lastPointIndex: 99,
|
||||
totalPoints: 100,
|
||||
expected: []int{33, 66, 99},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := handleTooManyForcedStops(tt.forcedStopIndexes, tt.days, tt.lastPointIndex, tt.totalPoints)
|
||||
if len(result) != len(tt.expected) {
|
||||
t.Errorf("Expected %d cuts, got %d", len(tt.expected), len(result))
|
||||
return
|
||||
}
|
||||
for i, cut := range result {
|
||||
if cut != tt.expected[i] {
|
||||
t.Errorf("Cut %d mismatch: expected %d, got %d", i, tt.expected[i], cut)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateSegmentsBetweenStops(t *testing.T) {
|
||||
// Create test points
|
||||
points := []TrackPoint{
|
||||
{Index: 0, Distance: 0, Elevation: 0},
|
||||
{Index: 1, Distance: 10, Elevation: 100},
|
||||
{Index: 2, Distance: 20, Elevation: 200},
|
||||
{Index: 3, Distance: 30, Elevation: 300},
|
||||
{Index: 4, Distance: 40, Elevation: 400},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
points []TrackPoint
|
||||
allStops []int
|
||||
elevFactor float64
|
||||
expected []struct {
|
||||
start, end int
|
||||
effort float64
|
||||
}
|
||||
}{
|
||||
{
|
||||
name: "No stops",
|
||||
points: points,
|
||||
allStops: []int{},
|
||||
elevFactor: 4.0,
|
||||
expected: []struct {
|
||||
start, end int
|
||||
effort float64
|
||||
}{},
|
||||
},
|
||||
{
|
||||
name: "One segment",
|
||||
points: points,
|
||||
allStops: []int{0, 4},
|
||||
elevFactor: 4.0,
|
||||
expected: []struct {
|
||||
start, end int
|
||||
effort float64
|
||||
}{
|
||||
{0, 4, 56.0}, // Effort: 40 + (400/100*4) = 56
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Multiple segments",
|
||||
points: points,
|
||||
allStops: []int{0, 2, 4},
|
||||
elevFactor: 4.0,
|
||||
expected: []struct {
|
||||
start, end int
|
||||
effort float64
|
||||
}{
|
||||
{0, 2, 28.0}, // Effort: 20 + (200/100*4) = 28
|
||||
{2, 4, 42.0}, // Effort: (30-20) + ((300-200)/100*4) + (40-30) + ((400-300)/100*4) = 42
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Duplicate stops (rest days)",
|
||||
points: points,
|
||||
allStops: []int{0, 2, 2, 4},
|
||||
elevFactor: 4.0,
|
||||
expected: []struct {
|
||||
start, end int
|
||||
effort float64
|
||||
}{
|
||||
{0, 2, 28.0}, // Effort: 20 + (200/100*4) = 28
|
||||
{2, 4, 42.0}, // Effort: (30-20) + ((300-200)/100*4) + (40-30) + ((400-300)/100*4) = 42
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := createSegmentsBetweenStops(tt.points, tt.allStops, tt.elevFactor)
|
||||
if len(result) != len(tt.expected) {
|
||||
t.Errorf("Expected %d segments, got %d", len(tt.expected), len(result))
|
||||
return
|
||||
}
|
||||
for i, seg := range result {
|
||||
if seg.start != tt.expected[i].start || seg.end != tt.expected[i].end {
|
||||
t.Errorf("Segment %d bounds mismatch: expected (%d,%d), got (%d,%d)",
|
||||
i, tt.expected[i].start, tt.expected[i].end, seg.start, seg.end)
|
||||
}
|
||||
if math.Abs(seg.effort-tt.expected[i].effort) > 0.1 {
|
||||
t.Errorf("Segment %d effort mismatch: expected %.1f, got %.1f",
|
||||
i, tt.expected[i].effort, seg.effort)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleTooManyCuts(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
cutIndexes []int
|
||||
forcedStopIndexes []int
|
||||
days int
|
||||
lastPointIndex int
|
||||
expectedLen int
|
||||
}{
|
||||
{
|
||||
name: "Fewer cuts than days",
|
||||
cutIndexes: []int{25, 50},
|
||||
forcedStopIndexes: []int{},
|
||||
days: 4,
|
||||
lastPointIndex: 99,
|
||||
expectedLen: 4, // Will add cuts to reach days
|
||||
},
|
||||
{
|
||||
name: "More cuts than days",
|
||||
cutIndexes: []int{20, 40, 60, 80},
|
||||
forcedStopIndexes: []int{},
|
||||
days: 3,
|
||||
lastPointIndex: 99,
|
||||
expectedLen: 3, // Will reduce to days
|
||||
},
|
||||
{
|
||||
name: "With forced stops",
|
||||
cutIndexes: []int{20, 40, 60, 80},
|
||||
forcedStopIndexes: []int{20, 60},
|
||||
days: 3,
|
||||
lastPointIndex: 99,
|
||||
expectedLen: 3, // Will keep forced stops and last point
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := handleTooManyCuts(tt.cutIndexes, tt.forcedStopIndexes, tt.days, tt.lastPointIndex)
|
||||
if len(result) != tt.expectedLen {
|
||||
t.Errorf("Expected %d cuts, got %d", tt.expectedLen, len(result))
|
||||
}
|
||||
|
||||
// Check that forced stops are included
|
||||
for _, forcedIdx := range tt.forcedStopIndexes {
|
||||
found := false
|
||||
for _, resultIdx := range result {
|
||||
if resultIdx == forcedIdx {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("Forced stop %d not found in result", forcedIdx)
|
||||
}
|
||||
}
|
||||
|
||||
// Check that last point is included
|
||||
lastPointFound := false
|
||||
for _, resultIdx := range result {
|
||||
if resultIdx == tt.lastPointIndex {
|
||||
lastPointFound = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !lastPointFound {
|
||||
t.Errorf("Last point %d not found in result", tt.lastPointIndex)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
14
run_all_tests.sh
Executable file
14
run_all_tests.sh
Executable file
@@ -0,0 +1,14 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -x
|
||||
|
||||
go test ./...
|
||||
|
||||
cd tests || exit 1
|
||||
|
||||
for file in *.sh; do
|
||||
if [ -d "$file" ]; then
|
||||
continue
|
||||
fi
|
||||
./$file
|
||||
done
|
79
tests/gpx/test_uneven_effort.gpx
Normal file
79
tests/gpx/test_uneven_effort.gpx
Normal file
@@ -0,0 +1,79 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<gpx version="1.1" creator="Test">
|
||||
<trk>
|
||||
<name>Test Route with Uneven Effort</name>
|
||||
<trkseg>
|
||||
<!-- Day 1: Relatively flat -->
|
||||
<trkpt lat="59.0" lon="18.0"><ele>10</ele></trkpt>
|
||||
<trkpt lat="59.1" lon="18.1"><ele>20</ele></trkpt>
|
||||
<trkpt lat="59.2" lon="18.2"><ele>30</ele></trkpt>
|
||||
<trkpt lat="59.3" lon="18.3"><ele>40</ele></trkpt>
|
||||
<trkpt lat="59.4" lon="18.4"><ele>50</ele></trkpt>
|
||||
<trkpt lat="59.5" lon="18.5"><ele>60</ele></trkpt>
|
||||
<trkpt lat="59.6" lon="18.6"><ele>70</ele></trkpt>
|
||||
<trkpt lat="59.7" lon="18.7"><ele>80</ele></trkpt>
|
||||
<trkpt lat="59.8" lon="18.8"><ele>90</ele></trkpt>
|
||||
<trkpt lat="59.9" lon="18.9"><ele>100</ele></trkpt>
|
||||
|
||||
<!-- Day 2: Some hills -->
|
||||
<trkpt lat="60.0" lon="19.0"><ele>120</ele></trkpt>
|
||||
<trkpt lat="60.1" lon="19.1"><ele>150</ele></trkpt>
|
||||
<trkpt lat="60.2" lon="19.2"><ele>180</ele></trkpt>
|
||||
<trkpt lat="60.3" lon="19.3"><ele>200</ele></trkpt>
|
||||
<trkpt lat="60.4" lon="19.4"><ele>220</ele></trkpt>
|
||||
<trkpt lat="60.5" lon="19.5"><ele>240</ele></trkpt>
|
||||
<trkpt lat="60.6" lon="19.6"><ele>260</ele></trkpt>
|
||||
<trkpt lat="60.7" lon="19.7"><ele>280</ele></trkpt>
|
||||
<trkpt lat="60.8" lon="19.8"><ele>300</ele></trkpt>
|
||||
<trkpt lat="60.9" lon="19.9"><ele>320</ele></trkpt>
|
||||
|
||||
<!-- Day 3: Moderate hills -->
|
||||
<trkpt lat="61.0" lon="20.0"><ele>340</ele></trkpt>
|
||||
<trkpt lat="61.1" lon="20.1"><ele>360</ele></trkpt>
|
||||
<trkpt lat="61.2" lon="20.2"><ele>380</ele></trkpt>
|
||||
<trkpt lat="61.3" lon="20.3"><ele>400</ele></trkpt>
|
||||
<trkpt lat="61.4" lon="20.4"><ele>420</ele></trkpt>
|
||||
<trkpt lat="61.5" lon="20.5"><ele>440</ele></trkpt>
|
||||
<trkpt lat="61.6" lon="20.6"><ele>460</ele></trkpt>
|
||||
<trkpt lat="61.7" lon="20.7"><ele>480</ele></trkpt>
|
||||
<trkpt lat="61.8" lon="20.8"><ele>500</ele></trkpt>
|
||||
<trkpt lat="61.9" lon="20.9"><ele>520</ele></trkpt>
|
||||
|
||||
<!-- Day 4: Steeper hills -->
|
||||
<trkpt lat="62.0" lon="21.0"><ele>550</ele></trkpt>
|
||||
<trkpt lat="62.1" lon="21.1"><ele>600</ele></trkpt>
|
||||
<trkpt lat="62.2" lon="21.2"><ele>650</ele></trkpt>
|
||||
<trkpt lat="62.3" lon="21.3"><ele>700</ele></trkpt>
|
||||
<trkpt lat="62.4" lon="21.4"><ele>750</ele></trkpt>
|
||||
<trkpt lat="62.5" lon="21.5"><ele>800</ele></trkpt>
|
||||
<trkpt lat="62.6" lon="21.6"><ele>850</ele></trkpt>
|
||||
<trkpt lat="62.7" lon="21.7"><ele>900</ele></trkpt>
|
||||
<trkpt lat="62.8" lon="21.8"><ele>950</ele></trkpt>
|
||||
<trkpt lat="62.9" lon="21.9"><ele>1000</ele></trkpt>
|
||||
|
||||
<!-- Day 5: Very steep section -->
|
||||
<trkpt lat="63.0" lon="22.0"><ele>1050</ele></trkpt>
|
||||
<trkpt lat="63.1" lon="22.1"><ele>1150</ele></trkpt>
|
||||
<trkpt lat="63.2" lon="22.2"><ele>1250</ele></trkpt>
|
||||
<trkpt lat="63.3" lon="22.3"><ele>1350</ele></trkpt>
|
||||
<trkpt lat="63.4" lon="22.4"><ele>1450</ele></trkpt>
|
||||
<trkpt lat="63.5" lon="22.5"><ele>1550</ele></trkpt>
|
||||
<trkpt lat="63.6" lon="22.6"><ele>1650</ele></trkpt>
|
||||
<trkpt lat="63.7" lon="22.7"><ele>1750</ele></trkpt>
|
||||
<trkpt lat="63.8" lon="22.8"><ele>1850</ele></trkpt>
|
||||
<trkpt lat="63.9" lon="22.9"><ele>1950</ele></trkpt>
|
||||
|
||||
<!-- Day 6: Extremely steep section -->
|
||||
<trkpt lat="64.0" lon="23.0"><ele>2050</ele></trkpt>
|
||||
<trkpt lat="64.1" lon="23.1"><ele>2200</ele></trkpt>
|
||||
<trkpt lat="64.2" lon="23.2"><ele>2350</ele></trkpt>
|
||||
<trkpt lat="64.3" lon="23.3"><ele>2500</ele></trkpt>
|
||||
<trkpt lat="64.4" lon="23.4"><ele>2650</ele></trkpt>
|
||||
<trkpt lat="64.5" lon="23.5"><ele>2800</ele></trkpt>
|
||||
<trkpt lat="64.6" lon="23.6"><ele>2950</ele></trkpt>
|
||||
<trkpt lat="64.7" lon="23.7"><ele>3100</ele></trkpt>
|
||||
<trkpt lat="64.8" lon="23.8"><ele>3250</ele></trkpt>
|
||||
<trkpt lat="64.9" lon="23.9"><ele>3400</ele></trkpt>
|
||||
</trkseg>
|
||||
</trk>
|
||||
</gpx>
|
36065
tests/gpx/very_long_route.gpx
Normal file
36065
tests/gpx/very_long_route.gpx
Normal file
File diff suppressed because it is too large
Load Diff
35
tests/test_forced_stop.sh
Executable file
35
tests/test_forced_stop.sh
Executable file
@@ -0,0 +1,35 @@
|
||||
#!/bin/bash
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
# Create a test GPX file with a simple route
|
||||
cat > test_route.gpx << EOF
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<gpx version="1.1" creator="Test">
|
||||
<trk>
|
||||
<name>Test Route</name>
|
||||
<trkseg>
|
||||
<trkpt lat="59.0" lon="18.0"><ele>10</ele></trkpt>
|
||||
<trkpt lat="59.1" lon="18.1"><ele>20</ele></trkpt>
|
||||
<trkpt lat="59.2" lon="18.2"><ele>30</ele></trkpt>
|
||||
<trkpt lat="59.3" lon="18.3"><ele>40</ele></trkpt>
|
||||
<trkpt lat="59.4" lon="18.4"><ele>50</ele></trkpt>
|
||||
<trkpt lat="59.5" lon="18.5"><ele>60</ele></trkpt>
|
||||
<trkpt lat="59.6" lon="18.6"><ele>70</ele></trkpt>
|
||||
<trkpt lat="59.7" lon="18.7"><ele>80</ele></trkpt>
|
||||
<trkpt lat="59.8" lon="18.8"><ele>90</ele></trkpt>
|
||||
<trkpt lat="59.9" lon="18.9"><ele>100</ele></trkpt>
|
||||
</trkseg>
|
||||
</trk>
|
||||
</gpx>
|
||||
EOF
|
||||
|
||||
# Run the bicycle planner with a 1-night forced stop
|
||||
echo "Testing with 1-night forced stop at 59.5,18.5"
|
||||
go run main.go --input="test_route.gpx" --days=5 --elevFactor=4 --forestRadius=5 --resupplyRadius=5000 --multiStop="59.5,18.5:1"
|
||||
|
||||
# The number of segments is now shown in the output
|
||||
echo "Check the output above for the number of segments created"
|
||||
|
||||
# Clean up
|
||||
rm test_route.gpx
|
35
tests/test_multi_night_stop.sh
Executable file
35
tests/test_multi_night_stop.sh
Executable file
@@ -0,0 +1,35 @@
|
||||
#!/bin/bash
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
# Create a test GPX file with a simple route
|
||||
cat > test_route.gpx << EOF
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<gpx version="1.1" creator="Test">
|
||||
<trk>
|
||||
<name>Test Route</name>
|
||||
<trkseg>
|
||||
<trkpt lat="59.0" lon="18.0"><ele>10</ele></trkpt>
|
||||
<trkpt lat="59.1" lon="18.1"><ele>20</ele></trkpt>
|
||||
<trkpt lat="59.2" lon="18.2"><ele>30</ele></trkpt>
|
||||
<trkpt lat="59.3" lon="18.3"><ele>40</ele></trkpt>
|
||||
<trkpt lat="59.4" lon="18.4"><ele>50</ele></trkpt>
|
||||
<trkpt lat="59.5" lon="18.5"><ele>60</ele></trkpt>
|
||||
<trkpt lat="59.6" lon="18.6"><ele>70</ele></trkpt>
|
||||
<trkpt lat="59.7" lon="18.7"><ele>80</ele></trkpt>
|
||||
<trkpt lat="59.8" lon="18.8"><ele>90</ele></trkpt>
|
||||
<trkpt lat="59.9" lon="18.9"><ele>100</ele></trkpt>
|
||||
</trkseg>
|
||||
</trk>
|
||||
</gpx>
|
||||
EOF
|
||||
|
||||
# Run the bicycle planner with a 2-night forced stop
|
||||
echo "Testing with 2-night forced stop at 59.5,18.5"
|
||||
go run main.go --input="test_route.gpx" --days=6 --elevFactor=4 --forestRadius=5 --resupplyRadius=5000 --multiStop="59.5,18.5:2"
|
||||
|
||||
# The number of segments is now shown in the output
|
||||
echo "Check the output above for the number of segments created"
|
||||
|
||||
# Clean up
|
||||
rm test_route.gpx
|
37
tests/test_town_avoidance.sh
Executable file
37
tests/test_town_avoidance.sh
Executable file
@@ -0,0 +1,37 @@
|
||||
#!/bin/bash
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
# Create a test GPX file with a simple route
|
||||
cat > test_route.gpx << EOF
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<gpx version="1.1" creator="Test">
|
||||
<trk>
|
||||
<name>Test Route</name>
|
||||
<trkseg>
|
||||
<trkpt lat="59.0" lon="18.0"><ele>10</ele></trkpt>
|
||||
<trkpt lat="59.1" lon="18.1"><ele>20</ele></trkpt>
|
||||
<trkpt lat="59.2" lon="18.2"><ele>30</ele></trkpt>
|
||||
<trkpt lat="59.3" lon="18.3"><ele>40</ele></trkpt>
|
||||
<trkpt lat="59.4" lon="18.4"><ele>50</ele></trkpt>
|
||||
<trkpt lat="59.5" lon="18.5"><ele>60</ele></trkpt>
|
||||
<trkpt lat="59.6" lon="18.6"><ele>70</ele></trkpt>
|
||||
<trkpt lat="59.7" lon="18.7"><ele>80</ele></trkpt>
|
||||
<trkpt lat="59.8" lon="18.8"><ele>90</ele></trkpt>
|
||||
<trkpt lat="59.9" lon="18.9"><ele>100</ele></trkpt>
|
||||
</trkseg>
|
||||
</trk>
|
||||
</gpx>
|
||||
EOF
|
||||
|
||||
# Run the bicycle planner with debug output to see town avoidance in action
|
||||
echo "Testing town avoidance feature with performance improvements"
|
||||
echo "Running first time (no cache)..."
|
||||
time go run main.go --input="test_route.gpx" --days=5 --elevFactor=4 --forestRadius=5 --resupplyRadius=5000
|
||||
|
||||
echo ""
|
||||
echo "Running second time (with cache)..."
|
||||
time go run main.go --input="test_route.gpx" --days=5 --elevFactor=4 --forestRadius=5 --resupplyRadius=5000
|
||||
|
||||
# Clean up
|
||||
rm test_route.gpx
|
6
tests/test_uneven_effort.sh
Executable file
6
tests/test_uneven_effort.sh
Executable file
@@ -0,0 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Test script for uneven effort distribution
|
||||
echo "Testing uneven effort distribution..."
|
||||
cd "$(dirname "$0")/.."
|
||||
go run main.go -input tests/gpx/test_uneven_effort.gpx -days 12 -elevFactor 4.0
|
8
tests/test_uneven_effort_with_stops.sh
Executable file
8
tests/test_uneven_effort_with_stops.sh
Executable file
@@ -0,0 +1,8 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Test script for uneven effort distribution with forced stops
|
||||
echo "Testing uneven effort distribution with forced stops..."
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
# Run the bicycle planner with the test GPX file and forced stops
|
||||
go run main.go -input tests/gpx/test_uneven_effort.gpx -days 12 -elevFactor 4.0 -multiStop="60.5,19.5:1;62.5,21.5:2;64.5,23.5:1"
|
9
tests/test_very_long_route_with_forced_stops.sh
Executable file
9
tests/test_very_long_route_with_forced_stops.sh
Executable file
@@ -0,0 +1,9 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Test script for the issue with uneven effort distribution
|
||||
echo "Testing issue with uneven effort distribution..."
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
# Run the bicycle planner with the specified parameters and debug logging
|
||||
# Filter the output to only show the relevant parts
|
||||
go run main.go -input tests/gpx/very_long_route.gpx -days 12 -forestRadius 5 -resupplyRadius 5000 -multiStop "59.126854,11.380377:1;59.92199,10.744629:2"
|
9
tests/test_very_long_route_with_forced_stops_and_more_days.sh
Executable file
9
tests/test_very_long_route_with_forced_stops_and_more_days.sh
Executable file
@@ -0,0 +1,9 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Test script for the issue with uneven effort distribution
|
||||
echo "Testing issue with uneven effort distribution..."
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
# Run the bicycle planner with the specified parameters and debug logging
|
||||
# Filter the output to only show the relevant parts
|
||||
go run main.go -input tests/gpx/very_long_route.gpx -days 14 -forestRadius 5 -resupplyRadius 5000 -multiStop "59.126854,11.380377:1;59.92199,10.744629:2"
|
Reference in New Issue
Block a user