Browse Source

Basic trips/gpx support

master
Jan-Lukas Else 3 weeks ago
parent
commit
1a6b1e6776
  1. 24
      editor.go
  2. 46
      geo.go
  3. 49
      geoMap.go
  4. 118
      geoTrips.go
  5. 59
      geoTrips_test.go
  6. 7
      go.mod
  7. 12
      go.sum
  8. 3
      http.go
  9. 2
      httpMiddlewares.go
  10. 16
      httpRouters.go
  11. 4
      pkgs/contenttype/contenttype.go
  12. 1
      render.go
  13. 6
      templates/assets/js/geomap.js
  14. 22
      templates/assets/js/geotrip.js
  15. 8
      templates/editor.gohtml
  16. 6
      templates/geomap.gohtml
  17. 2
      templates/post.gohtml
  18. 3
      templates/strings/de.yaml
  19. 3
      templates/strings/default.yaml
  20. 11
      templates/tripdetails.gohtml
  21. 6
      templates/tripheader.gohtml
  22. 1049
      testdata/test.gpx
  23. 4
      utils.go

24
editor.go

@ -143,7 +143,29 @@ func (a *goBlog) serveEditorPost(w http.ResponseWriter, r *http.Request) {
return
}
http.Redirect(w, r, post.Path, http.StatusFound)
return
case "helpgpx":
file, _, err := r.FormFile("file")
if err != nil {
a.serveError(w, r, err.Error(), http.StatusBadRequest)
return
}
gpx, err := io.ReadAll(file)
if err != nil {
a.serveError(w, r, err.Error(), http.StatusBadRequest)
return
}
var gpxBuffer bytes.Buffer
_, _ = a.min.Write(&gpxBuffer, contenttype.XML, gpx)
resultMap := map[string]string{
"gpx": gpxBuffer.String(),
}
resultBytes, err := yaml.Marshal(resultMap)
if err != nil {
a.serveError(w, r, err.Error(), http.StatusBadRequest)
return
}
w.Header().Set(contentType, contenttype.TextUTF8)
_, _ = w.Write(resultBytes)
default:
a.serveError(w, r, "Unknown editoraction", http.StatusBadRequest)
}

46
geo.go

@ -1,9 +1,12 @@
package main
import (
"embed"
"fmt"
"io"
"net/http"
"net/http/httptest"
"net/http/httputil"
"net/url"
"strings"
@ -70,3 +73,46 @@ func (a *goBlog) photonReverse(lat, lon float64, lang string) ([]byte, error) {
func geoOSMLink(g *gogeouri.Geo) string {
return fmt.Sprintf("https://www.openstreetmap.org/?mlat=%v&mlon=%v", g.Latitude, g.Longitude)
}
//go:embed leaflet/*
var leafletFiles embed.FS
func (a *goBlog) proxyTiles(basePath string) http.HandlerFunc {
osmUrl, _ := url.Parse("https://tile.openstreetmap.org/")
tileProxy := http.StripPrefix(basePath, httputil.NewSingleHostReverseProxy(osmUrl))
return func(w http.ResponseWriter, r *http.Request) {
proxyTarget := "https://tile.openstreetmap.org" + r.URL.Path
proxyRequest, _ := http.NewRequest(http.MethodGet, proxyTarget, nil)
// Copy request headers
for _, k := range []string{
"Accept-Encoding",
"Accept-Language",
"Accept",
"Cache-Control",
"If-Modified-Since",
"If-None-Match",
"User-Agent",
} {
proxyRequest.Header.Set(k, r.Header.Get(k))
}
rec := httptest.NewRecorder()
tileProxy.ServeHTTP(rec, proxyRequest)
res := rec.Result()
// Copy result headers
for _, k := range []string{
"Accept-Ranges",
"Access-Control-Allow-Origin",
"Age",
"Cache-Control",
"Content-Length",
"Content-Type",
"Etag",
"Expires",
} {
w.Header().Set(k, res.Header.Get(k))
}
w.WriteHeader(res.StatusCode)
_, _ = io.Copy(w, res.Body)
_ = res.Body.Close()
}
}

49
geoMap.go

@ -1,18 +1,10 @@
package main
import (
"embed"
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"net/http/httputil"
"net/url"
)
//go:embed leaflet/*
var leafletFiles embed.FS
const defaultGeoMapPath = "/map"
func (a *goBlog) serveGeoMap(w http.ResponseWriter, r *http.Request) {
@ -68,48 +60,7 @@ func (a *goBlog) serveGeoMap(w http.ResponseWriter, r *http.Request) {
BlogString: blog,
Canonical: a.getFullAddress(mapPath),
Data: map[string]interface{}{
"mappath": mapPath,
"locations": string(jb),
},
})
}
func (a *goBlog) proxyTiles(basePath string) http.HandlerFunc {
osmUrl, _ := url.Parse("https://tile.openstreetmap.org/")
tileProxy := http.StripPrefix(basePath, httputil.NewSingleHostReverseProxy(osmUrl))
return func(w http.ResponseWriter, r *http.Request) {
proxyTarget := "https://tile.openstreetmap.org" + r.URL.Path
proxyRequest, _ := http.NewRequest(http.MethodGet, proxyTarget, nil)
// Copy request headers
for _, k := range []string{
"Accept-Encoding",
"Accept-Language",
"Accept",
"Cache-Control",
"If-Modified-Since",
"If-None-Match",
"User-Agent",
} {
proxyRequest.Header.Set(k, r.Header.Get(k))
}
rec := httptest.NewRecorder()
tileProxy.ServeHTTP(rec, proxyRequest)
res := rec.Result()
// Copy result headers
for _, k := range []string{
"Accept-Ranges",
"Access-Control-Allow-Origin",
"Age",
"Cache-Control",
"Content-Length",
"Content-Type",
"Etag",
"Expires",
} {
w.Header().Set(k, res.Header.Get(k))
}
w.WriteHeader(res.StatusCode)
_, _ = io.Copy(w, res.Body)
_ = res.Body.Close()
}
}

118
geoTrips.go

@ -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
}

59
geoTrips_test.go

@ -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)
}

7
go.mod

@ -41,14 +41,16 @@ require (
github.com/stretchr/testify v1.7.0
github.com/tdewolff/minify/v2 v2.9.22
github.com/thoas/go-funk v0.9.1
github.com/tkrajina/gpxgo v1.1.2
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80
github.com/vcraescu/go-paginator v1.0.1-0.20201114172518-2cfc59fe05c2
github.com/yuin/goldmark v1.4.3
// master
github.com/yuin/goldmark-emoji v1.0.2-0.20210607094911-0487583eca38
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa
golang.org/x/net v0.0.0-20211108170745-6635138e15ea
golang.org/x/net v0.0.0-20211109214657-ef0fda0de508
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
golang.org/x/text v0.3.7
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
tailscale.com v1.16.2
// main
@ -102,8 +104,7 @@ require (
go4.org/intern v0.0.0-20210108033219-3eb7198706b2 // indirect
go4.org/mem v0.0.0-20201119185036-c04c5a6ff174 // indirect
go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222180813-1025295fd063 // indirect
golang.org/x/sys v0.0.0-20211109065445-02f5c0300f6e // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/sys v0.0.0-20211109184856-51b60fd695b3 // indirect
golang.org/x/time v0.0.0-20210611083556-38a9dc6acbc6 // indirect
golang.zx2c4.com/wireguard v0.0.0-20210905140043-2ef39d47540c // indirect
golang.zx2c4.com/wireguard/windows v0.4.10 // indirect

12
go.sum

@ -280,6 +280,7 @@ github.com/jlaffaye/ftp v0.0.0-20211029032751-b1140299f4df h1:nsRFf9ZkcalB12ZJZY
github.com/jlaffaye/ftp v0.0.0-20211029032751-b1140299f4df/go.mod h1:2lmrmq866uF2tnje75wQHzmPXhmSWUt7Gyx2vgK1RCU=
github.com/jlelse/feeds v1.2.1-0.20210704161900-189f94254ad4 h1:d2oKwfgLl3ef0PyYDkzjsVyYlBZzNpOpXitDraOnVXc=
github.com/jlelse/feeds v1.2.1-0.20210704161900-189f94254ad4/go.mod h1:vt0iOV52/wq97Ql/jp7mUkqyrlEiGQuhHic4bVoHy0c=
github.com/joeshaw/gengen v0.0.0-20190604015154-c77d87825f5a/go.mod h1:v2qvRL8Xwk4OlARK6gPlf2JreZXzv0dYp/8+kUJ0y7Q=
github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ=
github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
github.com/josharian/native v0.0.0-20200817173448-b6b71def0850 h1:uhL5Gw7BINiiPAo24A2sxkcDI0Jt/sqp1v5xQCniEFA=
@ -452,6 +453,8 @@ github.com/tdewolff/test v1.0.6 h1:76mzYJQ83Op284kMT+63iCNCI7NEERsIN8dLM+RiKr4=
github.com/tdewolff/test v1.0.6/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
github.com/thoas/go-funk v0.9.1 h1:O549iLZqPpTUQ10ykd26sZhzD+rmR5pWhuElrhbC20M=
github.com/thoas/go-funk v0.9.1/go.mod h1:+IWnUfUmFO1+WVYQWQtIJHeRRdaIyyYglZN7xzUPe4Q=
github.com/tkrajina/gpxgo v1.1.2 h1:il6rjS6IGm3yqa/yr7+fKBlF3ufWDEPZrYi/kxI1Jv0=
github.com/tkrajina/gpxgo v1.1.2/go.mod h1:795sjVRFo5wWyN6oOZp0RYienGGBJjpAlgOz2nCngA0=
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 h1:nrZ3ySNYwJbSpD6ce9duiP+QkD3JuLCcWkdaehUS/3Y=
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80/go.mod h1:iFyPdL66DjUD96XmzVL3ZntbzcflLnznH0fr99w5VqE=
github.com/u-root/uio v0.0.0-20210528114334-82958018845c h1:BFvcl34IGnw8yvJi8hlqLFo9EshRInwWBs2M5fGWzQA=
@ -587,8 +590,8 @@ golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210903162142-ad29c8ab022f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211108170745-6635138e15ea h1:FosBMXtOc8Tp9Hbo4ltl1WJSrTVewZU8MPnTPY2HdH8=
golang.org/x/net v0.0.0-20211108170745-6635138e15ea/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211109214657-ef0fda0de508 h1:v3NKo+t/Kc3EASxaKZ82lwK6mCf4ZeObQBduYFZHo7c=
golang.org/x/net v0.0.0-20211109214657-ef0fda0de508/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -687,8 +690,8 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211109065445-02f5c0300f6e h1:i6Vklmyu+fZMFYpum+sR4ZWABGW7MyIxfJZXYvcnbns=
golang.org/x/sys v0.0.0-20211109065445-02f5c0300f6e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211109184856-51b60fd695b3 h1:T6tyxxvHMj2L1R2kZg0uNMpS8ZhB9lRa9XRGTCSA65w=
golang.org/x/sys v0.0.0-20211109184856-51b60fd695b3/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210503060354-a79de5458b56 h1:b8jxX3zqjpqb2LklXPzKSGJhzyxCOZSz8ncv8Nv+y7w=
@ -717,6 +720,7 @@ golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190603231351-8aaa1484dc10/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=

3
http.go

@ -187,6 +187,9 @@ func (a *goBlog) buildRouter() (http.Handler, error) {
// Media files
r.Route("/m", a.mediaFilesRouter)
// Other routes
r.Route("/x", a.xRouter)
// Captcha
r.Handle("/captcha/*", captcha.Server(500, 250))

2
httpMiddlewares.go

@ -41,7 +41,7 @@ func (a *goBlog) securityHeaders(next http.Handler) http.Handler {
w.Header().Set("X-Content-Type-Options", "nosniff")
w.Header().Set("X-Frame-Options", "SAMEORIGIN")
w.Header().Set("X-Xss-Protection", "1; mode=block")
w.Header().Set("Content-Security-Policy", "default-src 'self'"+cspDomains)
w.Header().Set("Content-Security-Policy", "default-src 'self'"+cspDomains+"; img-src 'self' "+cspDomains+" data:")
next.ServeHTTP(w, r)
})
}

16
httpRouters.go

@ -96,6 +96,13 @@ func (a *goBlog) mediaFilesRouter(r chi.Router) {
r.Get(mediaFileRoute, a.serveMediaFile)
}
// Various other routes
func (a *goBlog) xRouter(r chi.Router) {
r.Use(a.privateModeHandler)
r.Get("/tiles/{z}/{x}/{y}.png", a.proxyTiles("/x/tiles"))
r.With(cacheLoggedIn, a.cacheMiddleware).HandleFunc("/leaflet/*", a.serveFs(leafletFiles, "/x/"))
}
// Blog
func (a *goBlog) blogRouter(blog string, conf *configBlog) func(r chi.Router) {
return func(r chi.Router) {
@ -398,14 +405,7 @@ func (a *goBlog) blogGeoMapRouter(conf *configBlog) func(r chi.Router) {
return func(r chi.Router) {
if mc := conf.Map; mc != nil && mc.Enabled {
mapPath := conf.getRelativePath(defaultIfEmpty(mc.Path, defaultGeoMapPath))
r.Route(mapPath, func(r chi.Router) {
r.Use(a.privateModeHandler)
r.Group(func(r chi.Router) {
r.With(a.cacheMiddleware).Get("/", a.serveGeoMap)
r.With(cacheLoggedIn, a.cacheMiddleware).HandleFunc("/leaflet/*", a.serveFs(leafletFiles, mapPath+"/"))
})
r.Get("/tiles/{z}/{x}/{y}.png", a.proxyTiles(mapPath+"/tiles"))
})
r.With(a.privateModeHandler, a.cacheMiddleware).Get(mapPath, a.serveGeoMap)
}
}
}

4
pkgs/contenttype/contenttype.go

@ -15,13 +15,15 @@ const (
LDJSON = "application/ld+json"
MultipartForm = "multipart/form-data"
RSS = "application/rss+xml"
Text = "text/plain"
WWWForm = "application/x-www-form-urlencoded"
XML = "text/xml"
ASUTF8 = AS + CharsetUtf8Suffix
CSSUTF8 = CSS + CharsetUtf8Suffix
HTMLUTF8 = HTML + CharsetUtf8Suffix
JSUTF8 = JS + CharsetUtf8Suffix
JSONUTF8 = JSON + CharsetUtf8Suffix
JSUTF8 = JS + CharsetUtf8Suffix
TextUTF8 = Text + CharsetUtf8Suffix
XMLUTF8 = XML + CharsetUtf8Suffix
)

1
render.go

@ -61,6 +61,7 @@ func (a *goBlog) initRendering() error {
"likelink": a.likeLink,
"liketitle": a.likeTitle,
"photolinks": a.photoLinks,
"rendertrip": a.renderTrip,
// Others
"dateformat": dateFormat,
"isodate": isoDateFormat,

6
templates/assets/js/geomap.js

@ -4,7 +4,7 @@
let map = L.map('map')
L.tileLayer(mapEl.dataset.tiles, {
L.tileLayer("/x/tiles/{z}/{x}/{y}.png", {
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
}).addTo(map)
@ -16,7 +16,5 @@
})
markers.push(marker)
})
map.fitBounds(markers)
map.zoomOut(2, { animate: false })
map.fitBounds(markers, { padding: [5, 5] })
})()

22
templates/assets/js/geotrip.js

@ -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: '&copy; <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] })
})()

8
templates/editor.gohtml

@ -44,6 +44,14 @@
<input id="geostatus" type="text" class="hide" readonly>
</form>
<h2>{{ string .Blog.Lang "gpxhelper" }}</h2>
<p>{{ string .Blog.Lang "gpxhelperdesc" }}</p>
<form class="fw p" method="post" enctype="multipart/form-data">
<input type="hidden" name="editoraction" value="helpgpx">
<input type="file" name="file">
<input type="submit" value="{{ string .Blog.Lang "upload" }}">
</form>
<script defer src="{{ asset "js/mdpreview.js" }}"></script>
<script defer src="{{ asset "js/geohelper.js" }}"></script>
<script defer src="{{ asset "js/formcache.js" }}"></script>

6
templates/geomap.gohtml

@ -1,8 +1,8 @@
{{ define "title" }}
<title>{{ mdtitle .Blog.Title }}</title>
{{ if not .Data.nolocations }}
<link rel="stylesheet" href="{{ .Data.mappath }}/leaflet/leaflet.css"/>
<script src="{{ .Data.mappath }}/leaflet/leaflet.js"></script>
<link rel="stylesheet" href="/x/leaflet/leaflet.css"/>
<script src="/x/leaflet/leaflet.js"></script>
{{ end }}
{{ end }}
@ -11,7 +11,7 @@
{{ if .Data.nolocations }}
<p>{{ string .Blog.Lang "nolocations" }}</p>
{{ else }}
<div class="p" id="map" data-locations="{{ .Data.locations }}" data-tiles="{{ .Data.mappath }}/tiles/{z}/{x}/{y}.png"></div>
<div class="p" id="map" data-locations="{{ .Data.locations }}"></div>
<script defer src="{{ asset "js/geomap.js" }}"></script>
{{ end }}
</main>

2
templates/post.gohtml

@ -2,6 +2,7 @@
<title>{{ with .Data.RenderedTitle }}{{ . }} - {{end}}{{ mdtitle .Blog.Title }}</title>
{{ include "postheadmeta" . }}
{{ with shorturl .Data }}<link rel="shortlink" href="{{ . }}">{{ end }}
{{ include "tripheader" . }}
{{ end }}
{{ define "main" }}
@ -17,6 +18,7 @@
{{ content .Data false }}
</div>
{{ end }}
{{ include "tripdetails" . }}
{{ include "posttax" . }}
</article>
{{ include "author" . }}

3
templates/strings/de.yaml

@ -18,8 +18,11 @@ editorpostdesc: "Leere Parameter werden automatisch entfernt. Mehr mögliche Par
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."
interactions: "Interaktionen & Kommentare"
interactionslabel: "Hast du eine Antwort hierzu veröffentlicht? Füge hier die URL ein."
kilometers: "Kilometer"
likeof: "Gefällt mir von"
loading: "Laden..."
location: "Standort"

3
templates/strings/default.yaml

@ -22,9 +22,12 @@ 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."
indieauth: "IndieAuth"
interactions: "Interactions & Comments"
interactionslabel: "Have you published a response to this? Paste the URL here."
kilometers: "kilometers"
likeof: "Like of"
loading: "Loading..."
location: "Location"

11
templates/tripdetails.gohtml

@ -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 }}

6
templates/tripheader.gohtml

@ -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 }}

1049
testdata/test.gpx

File diff suppressed because it is too large

4
utils.go

@ -220,8 +220,8 @@ func urlHasExt(rawUrl string, allowed ...string) (ext string, has bool) {
return ext, funk.ContainsString(allowed, strings.ToLower(ext))
}
// Get SHA-256 hash of file
func getSHA256(file io.ReadSeeker) (filename string, err error) {
// Get SHA-256 hash
func getSHA256(file io.ReadSeeker) (hash string, err error) {
if _, err = file.Seek(0, 0); err != nil {
return "", err
}

Loading…
Cancel
Save