Support GPX routes and waypoints as well

This commit is contained in:
Jan-Lukas Else 2021-11-16 18:01:11 +01:00
parent aabdde7d39
commit b906b55f72
12 changed files with 159 additions and 39 deletions

View File

@ -235,6 +235,7 @@ func (a *goBlog) editorPostDesc(blog string) string {
a.cfg.Micropub.PhotoDescriptionParam,
a.cfg.Micropub.ReplyParam,
a.cfg.Micropub.ReplyTitleParam,
gpxParameter,
} {
if param == "" {
continue

View File

@ -40,6 +40,7 @@ func (a *goBlog) serveGeoMap(w http.ResponseWriter, r *http.Request) {
type templateTrack struct {
Paths [][]*trackPoint
Points []*trackPoint
Post string
}
@ -56,6 +57,7 @@ func (a *goBlog) serveGeoMap(w http.ResponseWriter, r *http.Request) {
if t, err := a.getTrack(p); err == nil && t != nil {
tracks = append(tracks, &templateTrack{
Paths: t.Paths,
Points: t.Points,
Post: p.Path,
})
}

View File

@ -3,6 +3,7 @@ package main
import (
"encoding/json"
"errors"
"log"
"math"
"github.com/tkrajina/gpxgo/gpx"
@ -20,6 +21,8 @@ type trackResult struct {
HasPoints bool
Paths [][]*trackPoint
PathsJSON string
Points []*trackPoint
PointsJSON string
Kilometers string
Hours string
Name string
@ -34,7 +37,9 @@ func (a *goBlog) getTrack(p *post) (result *trackResult, err error) {
// Parse GPX
parseResult, err := trackParseGPX(gpxString)
if err != nil {
return nil, err
// Failed to parse, but just log error
log.Printf("failed to parse GPX: %v", err)
return nil, nil
}
l, _ := language.Parse(a.cfg.Blogs[p.Blog].Lang)
@ -45,18 +50,28 @@ func (a *goBlog) getTrack(p *post) (result *trackResult, err error) {
return nil, err
}
pointsJSON, err := json.Marshal(parseResult.points)
if err != nil {
return nil, err
}
result = &trackResult{
HasPoints: len(parseResult.paths) > 0 && len(parseResult.paths[0]) > 0,
Paths: parseResult.paths,
PathsJSON: string(pathsJSON),
Points: parseResult.points,
PointsJSON: string(pointsJSON),
Name: parseResult.gpxData.Name,
Kilometers: lp.Sprintf("%.2f", parseResult.md.MovingDistance/1000),
Hours: lp.Sprintf(
"%.0f:%2.0f:%2.0f",
}
if parseResult.md != nil {
result.Kilometers = lp.Sprintf("%.2f", parseResult.md.MovingDistance/1000)
result.Hours = lp.Sprintf(
"%.0f:%02.0f:%02.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
),
)
}
return result, nil
@ -68,6 +83,7 @@ type trackPoint struct {
type trackParseResult struct {
paths [][]*trackPoint
points []*trackPoint
gpxData *gpx.GPX
md *gpx.MovingData
}
@ -80,12 +96,12 @@ func trackParseGPX(gpxString string) (result *trackParseResult, err error) {
points []*trackPoint
}
var paths []*trackPath
result.gpxData, err = gpx.ParseString(gpxString)
if err != nil {
return nil, err
}
var paths []*trackPath
for _, track := range result.gpxData.Tracks {
for _, segment := range track.Segments {
md := segment.MovingData()
@ -100,17 +116,37 @@ func trackParseGPX(gpxString string) (result *trackParseResult, err error) {
paths = append(paths, path)
}
}
result.md = &gpx.MovingData{}
for _, path := range paths {
for _, route := range result.gpxData.Routes {
path := &trackPath{}
for _, point := range route.Points {
path.points = append(path.points, &trackPoint{
Lat: point.GetLatitude(), Lon: point.GetLongitude(),
})
}
paths = append(paths, path)
}
result.paths = make([][]*trackPoint, len(paths))
for i, path := range paths {
// Add points
result.paths[i] = path.points
// Combine moving data
if path.gpxMovingData != nil {
if result.md == nil {
result.md = &gpx.MovingData{}
}
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)
}
}
result.points = []*trackPoint{}
for _, point := range result.gpxData.Waypoints {
result.points = append(result.points, &trackPoint{
Lat: point.GetLatitude(), Lon: point.GetLongitude(),
})
}
return result, nil

View File

@ -30,9 +30,11 @@ func Test_geoTrack(t *testing.T) {
_ = app.initDatabase(false)
app.initComponents(false)
// First test (just with track)
gpxBytes, _ := os.ReadFile("testdata/test.gpx")
post := &post{
p := &post{
Blog: "en",
Parameters: map[string][]string{
"gpx": {
@ -41,19 +43,68 @@ func Test_geoTrack(t *testing.T) {
},
}
resEn, err := app.getTrack(post)
resEn, err := app.getTrack(p)
require.NoError(t, err)
assert.True(t, resEn.HasPoints)
assert.NotEmpty(t, resEn.Paths)
assert.Empty(t, resEn.Points)
assert.Equal(t, "2.70", resEn.Kilometers)
assert.Equal(t, "0:42:53", resEn.Hours)
post.Blog = "de"
p.Blog = "de"
resDe, err := app.getTrack(post)
resDe, err := app.getTrack(p)
require.NoError(t, err)
assert.True(t, resDe.HasPoints)
assert.NotEmpty(t, resDe.Paths)
assert.Empty(t, resDe.Points)
assert.Equal(t, "2,70", resDe.Kilometers)
assert.Equal(t, "0:42:53", resDe.Hours)
// Second file (with track and waypoint)
gpxBytes, _ = os.ReadFile("testdata/test2.gpx")
p = &post{
Blog: "en",
Parameters: map[string][]string{
"gpx": {
string(gpxBytes),
},
},
}
resEn, err = app.getTrack(p)
require.NoError(t, err)
assert.True(t, resEn.HasPoints)
assert.NotEmpty(t, resEn.Paths)
assert.NotEmpty(t, resEn.Points)
assert.Equal(t, "0.08", resEn.Kilometers)
assert.Equal(t, "0:01:29", resEn.Hours)
// Third file (just with route)
gpxBytes, _ = os.ReadFile("testdata/test3.gpx")
p = &post{
Blog: "en",
Parameters: map[string][]string{
"gpx": {
string(gpxBytes),
},
},
}
resEn, err = app.getTrack(p)
require.NoError(t, err)
assert.True(t, resEn.HasPoints)
assert.NotEmpty(t, resEn.Paths)
assert.Empty(t, resEn.Points)
assert.Equal(t, "", resEn.Kilometers)
assert.Equal(t, "", resEn.Hours)
}

View File

@ -134,7 +134,6 @@ func (a *goBlog) initChromaCSS() error {
// Write the CSS to the file
chromahtml.New(
chromahtml.ClassPrefix("c-"),
chromahtml.WithAllClasses(true),
).WriteCSS(chromaTempFile, chromaStyle)
// Close the file
_ = chromaTempFile.Close()

View File

@ -9,21 +9,26 @@
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
}).addTo(map)
let mapFeatures = []
let features = []
locations.forEach(loc => {
mapFeatures.push(L.marker([loc.Lat, loc.Lon]).addTo(map).on('click', function () {
features.push(L.marker([loc.Lat, loc.Lon]).addTo(map).on('click', function () {
window.open(loc.Post, '_blank').focus()
}))
})
tracks.forEach(track => {
track.Paths.forEach(path => {
mapFeatures.push(L.polyline(path.map(point => [point.Lat, point.Lon]), { color: 'blue' }).addTo(map).on('click', function () {
features.push(L.polyline(path.map(point => [point.Lat, point.Lon]), { color: 'blue' }).addTo(map).on('click', function () {
window.open(track.Post, '_blank').focus()
}))
})
track.Points.forEach(point => {
features.push(L.marker([point.Lat, point.Lon]).addTo(map).on('click', function () {
window.open(track.Post, '_blank').focus()
}))
})
})
map.fitBounds(L.featureGroup(mapFeatures).getBounds(), { padding: [5, 5] })
map.fitBounds(L.featureGroup(features).getBounds(), { padding: [5, 5] })
})()

View File

@ -1,6 +1,7 @@
(function () {
let mapEl = document.getElementById('map')
let paths = JSON.parse(mapEl.dataset.paths)
let paths = (mapEl.dataset.paths == "") ? [] : JSON.parse(mapEl.dataset.paths)
let points = (mapEl.dataset.points == "") ? [] : JSON.parse(mapEl.dataset.points)
let map = L.map('map')
@ -8,11 +9,15 @@
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
}).addTo(map)
let polylines = []
let features = []
paths.forEach(path => {
polylines.push(L.polyline(path.map(point => [point.Lat, point.Lon]), { color: 'blue' }).addTo(map))
features.push(L.polyline(path.map(point => [point.Lat, point.Lon]), { color: 'blue' }).addTo(map))
})
map.fitBounds(L.featureGroup(polylines).getBounds(), { padding: [5, 5] })
points.forEach(point => {
features.push(L.marker([point.Lat, point.Lon]).addTo(map))
})
map.fitBounds(L.featureGroup(features).getBounds(), { padding: [5, 5] })
})()

View File

@ -14,12 +14,12 @@ docomment: "Kommentieren"
download: "Herunterladen"
drafts: "Entwürfe"
editor: "Editor"
editorpostdesc: "Leere Parameter werden automatisch entfernt. Mehr mögliche Parameter: %s. Mögliche Zustände für `%s`: %s."
editorpostdesc: "💡 Leere Parameter werden automatisch entfernt. Mehr mögliche Parameter: %s. Mögliche Zustände für `%s`: %s."
emailopt: "E-Mail (optional)"
fileuses: "Datei-Verwendungen"
gentts: "Text-To-Speech-Audio erzeugen"
gpxhelper: "GPX-Helfer"
gpxhelperdesc: "GPX minimieren und YAML für das Frontmatter generieren."
gpxhelperdesc: "💡 GPX minimieren und YAML für das Frontmatter generieren."
interactions: "Interaktionen & Kommentare"
interactionslabel: "Hast du eine Antwort hierzu veröffentlicht? Füge hier die URL ein."
kilometers: "Kilometer"

View File

@ -17,13 +17,13 @@ docomment: "Comment"
download: "Download"
drafts: "Drafts"
editor: "Editor"
editorpostdesc: "Empty parameters are removed automatically. More possible parameters: %s. Possible states for `%s`: %s."
editorpostdesc: "💡 Empty parameters are removed automatically. More possible parameters: %s. Possible states for `%s`: %s."
emailopt: "Email (optional)"
feed: "Feed"
fileuses: "file uses"
gentts: "Generate Text-To-Speech audio"
gpxhelper: "GPX helper"
gpxhelperdesc: "Minify GPX and generate YAML for the frontmatter."
gpxhelperdesc: "💡 Minify GPX and generate YAML for the frontmatter."
indieauth: "IndieAuth"
interactions: "Interactions & Comments"
interactionslabel: "Have you published a response to this? Paste the URL here."

View File

@ -1,11 +1,13 @@
{{ define "trackdetails" }}
{{ if .Data.HasTrack }}
{{ $track := (gettrack .Data) }}
{{ if $track }}
{{ if $track.HasPoints }}
{{ $lang := .Blog.Lang }}
<p>{{ with $track.Name }}<b>{{ . }}</b> {{ end }}{{ with $track.Kilometers }}🏁 {{ . }} {{ string $lang "kilometers" }} {{ end }}{{ with $track.Hours }}⌛ {{ . }}{{ end }}</p>
<div class="p" id="map" data-paths="{{ $track.PathsJSON }}" data-tiles="/tiles/{z}/{x}/{y}.png"></div>
<div class="p" id="map" data-paths="{{ $track.PathsJSON }}" data-points="{{ $track.PointsJSON }}"></div>
<script defer src="{{ asset "js/geotrack.js" }}"></script>
{{ end }}
{{ end }}
{{ end }}
{{ end }}

17
testdata/test2.gpx vendored Normal file
View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<gpx version="1.0">
<name>Example gpx</name>
<wpt lat="46.57638889" lon="8.89263889">
<ele>2372</ele>
<name>LAGORETICO</name>
</wpt>
<trk><name>Example gpx</name><number>1</number><trkseg>
<trkpt lat="46.57608333" lon="8.89241667"><ele>2376</ele><time>2007-10-14T10:09:57Z</time></trkpt>
<trkpt lat="46.57619444" lon="8.89252778"><ele>2375</ele><time>2007-10-14T10:10:52Z</time></trkpt>
<trkpt lat="46.57641667" lon="8.89266667"><ele>2372</ele><time>2007-10-14T10:12:39Z</time></trkpt>
<trkpt lat="46.57650000" lon="8.89280556"><ele>2373</ele><time>2007-10-14T10:13:12Z</time></trkpt>
<trkpt lat="46.57638889" lon="8.89302778"><ele>2374</ele><time>2007-10-14T10:13:20Z</time></trkpt>
<trkpt lat="46.57652778" lon="8.89322222"><ele>2375</ele><time>2007-10-14T10:13:48Z</time></trkpt>
<trkpt lat="46.57661111" lon="8.89344444"><ele>2376</ele><time>2007-10-14T10:14:08Z</time></trkpt>
</trkseg></trk>
</gpx>

2
testdata/test3.gpx vendored Normal file

File diff suppressed because one or more lines are too long