From 45a38e39e888c59c7ea27c015c88b3b7702a676f Mon Sep 17 00:00:00 2001 From: Jan-Lukas Else Date: Wed, 24 Jul 2024 18:23:20 +0200 Subject: [PATCH] Render SVG shape of the GPX route when "showroute: false" and in editor preview --- geoMap.go | 2 +- geoTiles.go | 2 +- geoTiles_test.go | 2 +- geoTrack.go | 60 ++++++++-------------- geoTrack_test.go | 27 ++-------- original-assets/styles/styles.scss | 2 +- templates/assets/css/styles.css | 2 +- ui.go | 4 +- uiComponents.go | 81 ++++++++++++++++++++++++------ 9 files changed, 98 insertions(+), 84 deletions(-) diff --git a/geoMap.go b/geoMap.go index 1ad32422..eb9c6e75 100644 --- a/geoMap.go +++ b/geoMap.go @@ -87,7 +87,7 @@ func (a *goBlog) serveGeoMapTracks(w http.ResponseWriter, r *http.Request) { var tracks []*templateTrack for _, p := range allPostsWithTracks { - if t, err := a.getTrack(p, true); err == nil && t != nil { + if t, err := a.getTrack(p); err == nil && t != nil { tracks = append(tracks, &templateTrack{ Paths: t.Paths, Points: t.Points, diff --git a/geoTiles.go b/geoTiles.go index f442a05e..cee25b9a 100644 --- a/geoTiles.go +++ b/geoTiles.go @@ -70,7 +70,7 @@ func (a *goBlog) getMaxZoom() int { if c := a.cfg.MapTiles; c != nil && c.MaxZoom > 0 { return c.MaxZoom } - return 20 + return 19 } func (a *goBlog) getMapAttribution() string { diff --git a/geoTiles_test.go b/geoTiles_test.go index c977e4f2..d3a42679 100644 --- a/geoTiles_test.go +++ b/geoTiles_test.go @@ -73,7 +73,7 @@ func Test_getMaxZoom(t *testing.T) { cfg: &config{}, } - assert.Equal(t, 20, app.getMaxZoom()) + assert.Equal(t, 19, app.getMaxZoom()) app.cfg.MapTiles = &configMapTiles{ MaxZoom: 10, diff --git a/geoTrack.go b/geoTrack.go index 9c2e4a66..fa27c57f 100644 --- a/geoTrack.go +++ b/geoTrack.go @@ -1,7 +1,6 @@ package main import ( - "encoding/json" "errors" "math" @@ -25,24 +24,24 @@ func (p *post) showTrackRoute() bool { } type trackResult struct { - Paths [][]*trackPoint - PathsJSON string - Points []*trackPoint - PointsJSON string - Kilometers string - Hours string - Uphill string - Downhill string - Name string - MapAttribution string - MinZoom, MaxZoom int + Paths [][]*trackPoint + Points []*trackPoint + Kilometers string + Hours string + Uphill string + Downhill string + Name string +} + +func (t *trackResult) hasPath() bool { + return t.Paths != nil } func (t *trackResult) hasMapFeatures() bool { - return t.Points != nil || t.Paths != nil + return t.Points != nil || t.hasPath() } -func (a *goBlog) getTrack(p *post, withMapFeatures bool) (result *trackResult, err error) { +func (a *goBlog) getTrack(p *post) (result *trackResult, err error) { gpxString := p.firstParameter(gpxParameter) if gpxString == "" { return nil, errors.New("no gpx parameter in post") @@ -63,27 +62,11 @@ func (a *goBlog) getTrack(p *post, withMapFeatures bool) (result *trackResult, e Name: parseResult.gpxData.Name, } - if withMapFeatures { - // Add Paths - pathsJSON, err := json.Marshal(parseResult.paths) - if err != nil { - return nil, err - } - result.Paths = parseResult.paths - result.PathsJSON = string(pathsJSON) - // Add Points - pointsJSON, err := json.Marshal(parseResult.points) - if err != nil { - return nil, err - } - result.Points = parseResult.points - result.PointsJSON = string(pointsJSON) - // Map settings - result.MapAttribution = a.getMapAttribution() - result.MinZoom = a.getMinZoom() - result.MaxZoom = a.getMaxZoom() - } - + // Add Paths + result.Paths = parseResult.paths + // Add Points + result.Points = parseResult.points + // Calculate moving statistics if parseResult.md != nil { result.Kilometers = lp.Sprintf("%.2f", parseResult.md.MovingDistance/1000) result.Hours = lp.Sprintf( @@ -93,7 +76,6 @@ func (a *goBlog) getTrack(p *post, withMapFeatures bool) (result *trackResult, e math.Floor(math.Mod(parseResult.md.MovingTime, 60)), // Seconds ) } - if parseResult.ud != nil { result.Uphill = lp.Sprintf("%.0f", parseResult.ud.Uphill) result.Downhill = lp.Sprintf("%.0f", parseResult.ud.Downhill) @@ -139,7 +121,7 @@ func trackParseGPX(gpxString string) (result *trackParseResult, err error) { } for _, point := range segment.Points { path.points = append(path.points, &trackPoint{ - Lat: point.GetLatitude(), Lon: point.GetLongitude(), + Lat: point.Latitude, Lon: point.Longitude, }) } paths = append(paths, path) @@ -149,7 +131,7 @@ func trackParseGPX(gpxString string) (result *trackParseResult, err error) { path := &trackPath{} for _, point := range route.Points { path.points = append(path.points, &trackPoint{ - Lat: point.GetLatitude(), Lon: point.GetLongitude(), + Lat: point.Latitude, Lon: point.Longitude, }) } paths = append(paths, path) @@ -182,7 +164,7 @@ func trackParseGPX(gpxString string) (result *trackParseResult, err error) { result.points = []*trackPoint{} for _, point := range result.gpxData.Waypoints { result.points = append(result.points, &trackPoint{ - Lat: point.GetLatitude(), Lon: point.GetLongitude(), + Lat: point.Latitude, Lon: point.Longitude, }) } diff --git a/geoTrack_test.go b/geoTrack_test.go index 1f4f4167..22e0d2c8 100644 --- a/geoTrack_test.go +++ b/geoTrack_test.go @@ -36,7 +36,7 @@ func Test_geoTrack(t *testing.T) { }, } - resEn, err := app.getTrack(p, true) + resEn, err := app.getTrack(p) require.NoError(t, err) assert.NotEmpty(t, resEn.Paths) @@ -46,7 +46,7 @@ func Test_geoTrack(t *testing.T) { p.Blog = "de" - resDe, err := app.getTrack(p, true) + resDe, err := app.getTrack(p) require.NoError(t, err) assert.NotEmpty(t, resDe.Paths) @@ -67,7 +67,7 @@ func Test_geoTrack(t *testing.T) { }, } - resEn, err = app.getTrack(p, true) + resEn, err = app.getTrack(p) require.NoError(t, err) assert.NotEmpty(t, resEn.Paths) @@ -75,25 +75,6 @@ func Test_geoTrack(t *testing.T) { assert.Equal(t, "0.08", resEn.Kilometers) assert.Equal(t, "0:01:29", resEn.Hours) - // Test "privacy" feature to hide track - - p = &post{ - Blog: "en", - Parameters: map[string][]string{ - "gpx": {string(gpxBytes)}, - "showroute": {"false"}, - }, - } - - resEn, err = app.getTrack(p, p.showTrackRoute()) - require.NoError(t, err) - - assert.False(t, p.showTrackRoute()) - assert.Empty(t, resEn.Paths) - assert.Empty(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") @@ -107,7 +88,7 @@ func Test_geoTrack(t *testing.T) { }, } - resEn, err = app.getTrack(p, true) + resEn, err = app.getTrack(p) require.NoError(t, err) assert.NotEmpty(t, resEn.Paths) diff --git a/original-assets/styles/styles.scss b/original-assets/styles/styles.scss index dfcb6e03..d2f76753 100644 --- a/original-assets/styles/styles.scss +++ b/original-assets/styles/styles.scss @@ -36,11 +36,11 @@ $colors: ( } html { + scrollbar-color: var(--primary) transparent; @include lightmode; @media (prefers-color-scheme: dark) { @include darkmode; } - scrollbar-color: var(--primary) transparent; } body { diff --git a/templates/assets/css/styles.css b/templates/assets/css/styles.css index 0320d438..96f022c1 100644 --- a/templates/assets/css/styles.css +++ b/templates/assets/css/styles.css @@ -7,10 +7,10 @@ } html { + scrollbar-color: var(--primary) transparent; --background: #fff; --primary: #000; color: #000; - scrollbar-color: var(--primary) transparent; } @media (prefers-color-scheme: dark) { html { diff --git a/ui.go b/ui.go index 5a367621..64f5cbc2 100644 --- a/ui.go +++ b/ui.go @@ -18,7 +18,7 @@ func (a *goBlog) renderEditorPreview(hb *htmlbuilder.HtmlBuilder, bc *configBlog a.renderPostTitle(hb, p) a.renderPostMeta(hb, p, bc, "preview") a.postHtmlToWriter(hb, &postHtmlOptions{p: p, absolute: true}) - // a.renderPostGPX(hb, p, bc) + a.renderPostTrack(hb, p, bc, true) a.renderPostTax(hb, p, bc) } @@ -919,7 +919,7 @@ func (a *goBlog) renderPost(hb *htmlbuilder.HtmlBuilder, rd *renderData) { // External Videp a.renderPostVideo(hb, p) // GPS Track - a.renderPostGPX(hb, p, rd.Blog) + a.renderPostTrack(hb, p, rd.Blog, false) // Taxonomies a.renderPostTax(hb, p, rd.Blog) hb.WriteElementClose("article") diff --git a/uiComponents.go b/uiComponents.go index 03e71f46..5efc6848 100644 --- a/uiComponents.go +++ b/uiComponents.go @@ -2,7 +2,9 @@ package main import ( "cmp" + "encoding/json" "fmt" + "math" "strings" "time" @@ -476,15 +478,23 @@ func (*goBlog) renderPostTitle(hb *htmlbuilder.HtmlBuilder, p *post) { hb.WriteElementClose("h1") } -func (a *goBlog) renderPostGPX(hb *htmlbuilder.HtmlBuilder, p *post, b *configBlog) { +func (a *goBlog) renderPostTrack(hb *htmlbuilder.HtmlBuilder, p *post, b *configBlog, disableInteractiveMap bool) { if p == nil || !p.hasTrack() { return } - track, err := a.getTrack(p, p.showTrackRoute()) + track, err := a.getTrack(p) if err != nil || track == nil { return } - // Track stats + a.renderPostTrackStatistics(hb, track, b) + if !disableInteractiveMap && p.showTrackRoute() && track.hasMapFeatures() { + a.renderPostTrackMap(hb, track) + } else if track.hasPath() { + a.renderPostTrackSVG(hb, track) + } +} + +func (a *goBlog) renderPostTrackStatistics(hb *htmlbuilder.HtmlBuilder, track *trackResult, b *configBlog) { hb.WriteElementOpen("p") if track.Name != "" { hb.WriteElementOpen("strong") @@ -518,19 +528,60 @@ func (a *goBlog) renderPostGPX(hb *htmlbuilder.HtmlBuilder, p *post, b *configBl hb.WriteEscaped(a.ts.GetTemplateStringVariant(b.Lang, "meters")) } hb.WriteElementClose("p") - // Map (only show if it has features) - if track.hasMapFeatures() { - hb.WriteElementOpen( - "div", "id", "map", "class", "p", - "data-paths", track.PathsJSON, - "data-points", track.PointsJSON, - "data-minzoom", track.MinZoom, "data-maxzoom", track.MaxZoom, - "data-attribution", track.MapAttribution, - ) - hb.WriteElementClose("div") - hb.WriteElementOpen("script", "defer", "", "src", a.assetFileName("js/geomap.js")) - hb.WriteElementClose("script") +} + +func (a *goBlog) renderPostTrackMap(hb *htmlbuilder.HtmlBuilder, track *trackResult) { + pathsJSON, _ := json.Marshal(track.Paths) + pointsJSON, _ := json.Marshal(track.Points) + hb.WriteElementOpen( + "div", "id", "map", "class", "p", + "data-paths", string(pathsJSON), + "data-points", string(pointsJSON), + "data-minzoom", a.getMinZoom(), + "data-maxzoom", a.getMaxZoom(), + "data-attribution", a.getMapAttribution(), + ) + hb.WriteElementClose("div") + hb.WriteElementOpen("script", "defer", "", "src", a.assetFileName("js/geomap.js")) + hb.WriteElementClose("script") +} + +func (a *goBlog) renderPostTrackSVG(hb *htmlbuilder.HtmlBuilder, track *trackResult) { + const width, height = 700.0, 400.0 + // Calculate min/max values + minLat, minLon := math.Inf(1), math.Inf(1) // Positive infinity + maxLat, maxLon := math.Inf(-1), math.Inf(-1) // Negative infinity + for _, path := range track.Paths { + for _, point := range path { + minLat = math.Min(minLat, point.Lat) + maxLat = math.Max(maxLat, point.Lat) + minLon = math.Min(minLon, point.Lon) + maxLon = math.Max(maxLon, point.Lon) + } + } + // Calculate scaling and offsets + dataAspectRatio := (maxLon - minLon) / (maxLat - minLat) + svgAspectRatio := width / height + var scale, xOffset, yOffset float64 + if dataAspectRatio > svgAspectRatio { + scale = width / (maxLon - minLon) + yOffset = (height - (maxLat-minLat)*scale) / 2 + } else { + scale = height / (maxLat - minLat) + xOffset = (width - (maxLon-minLon)*scale) / 2 + } + // Generate SVG + hb.WriteElementOpen("svg", "width", "100%", "viewbox", fmt.Sprintf("0 0 %.0f %.0f", width, height)) + for _, path := range track.Paths { + hb.WriteString(``) } + hb.WriteElementClose("svg") } func (a *goBlog) renderPostReactions(hb *htmlbuilder.HtmlBuilder, p *post) {