mirror of https://github.com/jlelse/GoBlog
Support GPX routes and waypoints as well
This commit is contained in:
parent
aabdde7d39
commit
b906b55f72
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
|
|
60
geoTrack.go
60
geoTrack.go
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -9,21 +9,26 @@
|
|||
attribution: '© <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] })
|
||||
})()
|
|
@ -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: '© <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] })
|
||||
})()
|
|
@ -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"
|
||||
|
|
|
@ -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."
|
||||
|
|
|
@ -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 }}
|
|
@ -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>
|
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue