mirror of https://github.com/jlelse/GoBlog
Basic trips/gpx support
parent
95233038f4
commit
1a6b1e6776
@ -0,0 +1,118 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"log"
|
||||
"math"
|
||||
|
||||
"github.com/tkrajina/gpxgo/gpx"
|
||||
"golang.org/x/text/language"
|
||||
"golang.org/x/text/message"
|
||||
)
|
||||
|
||||
func (p *post) HasTrip() bool {
|
||||
return p.firstParameter("gpx") != ""
|
||||
}
|
||||
|
||||
type tripRenderResult struct {
|
||||
HasPoints bool
|
||||
Paths [][]*tripPoint
|
||||
PathsJSON string
|
||||
Kilometers string
|
||||
Hours string
|
||||
Name string
|
||||
}
|
||||
|
||||
func (a *goBlog) renderTrip(p *post) (result *tripRenderResult, err error) {
|
||||
gpxString := p.firstParameter("gpx")
|
||||
if gpxString == "" {
|
||||
return nil, errors.New("no gpx parameter in post")
|
||||
}
|
||||
|
||||
// Parse GPX
|
||||
parseResult, err := tripParseGPX(gpxString)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
l, _ := language.Parse(a.cfg.Blogs[p.Blog].Lang)
|
||||
lp := message.NewPrinter(l)
|
||||
|
||||
pathsJSON, err := json.Marshal(parseResult.paths)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result = &tripRenderResult{
|
||||
HasPoints: len(parseResult.paths) > 0 && len(parseResult.paths[0]) > 0,
|
||||
Paths: parseResult.paths,
|
||||
PathsJSON: string(pathsJSON),
|
||||
Name: parseResult.gpxData.Name,
|
||||
Kilometers: lp.Sprintf("%.2f", parseResult.md.MovingDistance/1000),
|
||||
Hours: lp.Sprintf(
|
||||
"%.0f:%2.0f:%2.0f",
|
||||
math.Floor(parseResult.md.MovingTime/3600), // Hours
|
||||
math.Floor(math.Mod(parseResult.md.MovingTime, 3600)/60), // Minutes
|
||||
math.Floor(math.Mod(parseResult.md.MovingTime, 60)), // Seconds
|
||||
),
|
||||
}
|
||||
|
||||
log.Println(string(pathsJSON))
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
type tripPoint struct {
|
||||
Lat, Lon float64
|
||||
}
|
||||
|
||||
type tripParseResult struct {
|
||||
paths [][]*tripPoint
|
||||
gpxData *gpx.GPX
|
||||
md *gpx.MovingData
|
||||
}
|
||||
|
||||
func tripParseGPX(gpxString string) (result *tripParseResult, err error) {
|
||||
result = &tripParseResult{}
|
||||
|
||||
type tripPath struct {
|
||||
gpxMovingData *gpx.MovingData
|
||||
points []*tripPoint
|
||||
}
|
||||
|
||||
var paths []*tripPath
|
||||
|
||||
result.gpxData, err = gpx.ParseString(gpxString)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, track := range result.gpxData.Tracks {
|
||||
for _, segment := range track.Segments {
|
||||
md := segment.MovingData()
|
||||
path := &tripPath{
|
||||
gpxMovingData: &md,
|
||||
}
|
||||
for _, point := range segment.Points {
|
||||
path.points = append(path.points, &tripPoint{
|
||||
Lat: point.GetLatitude(), Lon: point.GetLongitude(),
|
||||
})
|
||||
}
|
||||
paths = append(paths, path)
|
||||
}
|
||||
}
|
||||
|
||||
result.md = &gpx.MovingData{}
|
||||
for _, path := range paths {
|
||||
// Combine moving data
|
||||
result.md.MaxSpeed = math.Max(result.md.MaxSpeed, path.gpxMovingData.MaxSpeed)
|
||||
result.md.MovingDistance = result.md.MovingDistance + path.gpxMovingData.MovingDistance
|
||||
result.md.MovingTime = result.md.MovingTime + path.gpxMovingData.MovingTime
|
||||
result.md.StoppedDistance = result.md.StoppedDistance + path.gpxMovingData.StoppedDistance
|
||||
result.md.StoppedTime = result.md.StoppedTime + path.gpxMovingData.StoppedTime
|
||||
// Add points
|
||||
result.paths = append(result.paths, path.points)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_renderTrip(t *testing.T) {
|
||||
app := &goBlog{
|
||||
cfg: &config{
|
||||
Db: &configDb{
|
||||
File: filepath.Join(t.TempDir(), "test.db"),
|
||||
},
|
||||
Server: &configServer{},
|
||||
Blogs: map[string]*configBlog{
|
||||
"en": {
|
||||
Lang: "en",
|
||||
},
|
||||
"de": {
|
||||
Lang: "de",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
_ = app.initDatabase(false)
|
||||
app.initComponents(false)
|
||||
|
||||
gpxBytes, _ := os.ReadFile("testdata/test.gpx")
|
||||
|
||||
post := &post{
|
||||
Blog: "en",
|
||||
Parameters: map[string][]string{
|
||||
"gpx": {
|
||||
string(gpxBytes),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
resEn, err := app.renderTrip(post)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.True(t, resEn.HasPoints)
|
||||
assert.Equal(t, "2.70", resEn.Kilometers)
|
||||
assert.Equal(t, "0:42:53", resEn.Hours)
|
||||
|
||||
post.Blog = "de"
|
||||
|
||||
resDe, err := app.renderTrip(post)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.True(t, resDe.HasPoints)
|
||||
assert.Equal(t, "2,70", resDe.Kilometers)
|
||||
assert.Equal(t, "0:42:53", resDe.Hours)
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
(function () {
|
||||
let mapEl = document.getElementById('map')
|
||||
let paths = JSON.parse(mapEl.dataset.paths)
|
||||
|
||||
let map = L.map('map')
|
||||
|
||||
L.tileLayer("/x/tiles/{z}/{x}/{y}.png", {
|
||||
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
||||
}).addTo(map)
|
||||
|
||||
let polylines = []
|
||||
paths.forEach(path => {
|
||||
let pathPoints = []
|
||||
path.forEach(point => {
|
||||
pathPoints.push([point.Lat, point.Lon])
|
||||
})
|
||||
let pl = L.polyline(pathPoints, { color: 'blue' }).addTo(map)
|
||||
polylines.push(pl)
|
||||
})
|
||||
let fgb = L.featureGroup(polylines).getBounds()
|
||||
map.fitBounds(fgb, { padding: [5, 5] })
|
||||
})()
|
@ -0,0 +1,11 @@
|
||||
{{ define "tripdetails" }}
|
||||
{{ if .Data.HasTrip }}
|
||||
{{ $trip := (rendertrip .Data) }}
|
||||
{{ if $trip.HasPoints }}
|
||||
{{ $lang := .Blog.Lang }}
|
||||
<p>{{ with $trip.Name }}<b>{{ . }}</b> {{ end }}{{ with $trip.Kilometers }}🏁 {{ . }} {{ string $lang "kilometers" }} {{ end }}{{ with $trip.Hours }}⌛ {{ . }}{{ end }}</p>
|
||||
<div class="p" id="map" data-paths="{{ $trip.PathsJSON }}" data-tiles="/tiles/{z}/{x}/{y}.png"></div>
|
||||
<script defer src="{{ asset "js/geotrip.js" }}"></script>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ end }}
|
@ -0,0 +1,6 @@
|
||||
{{ define "tripheader" }}
|
||||
{{ if .Data.HasTrip }}
|
||||
<link rel="stylesheet" href="/x/leaflet/leaflet.css"/>
|
||||
<script src="/x/leaflet/leaflet.js"></script>
|
||||
{{ end }}
|
||||
{{ end }}
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue