Browse Source

Make map tiles configurable

Closes #5
master
Jan-Lukas Else 2 weeks ago
parent
commit
783afc1c3e
  1. 8
      config.go
  2. 7
      example-config.yml
  3. 43
      geo.go
  4. 7
      geoMap.go
  5. 82
      geoTiles.go
  6. 95
      geoTiles_test.go
  7. 33
      geoTrack.go
  8. 2
      httpRouters.go
  9. 9
      templates/assets/js/geomap.js
  10. 9
      templates/assets/js/geotrack.js
  11. 8
      templates/geomap.gohtml
  12. 8
      templates/trackdetails.gohtml

8
config.go

@ -26,6 +26,7 @@ type config struct {
PrivateMode *configPrivateMode `mapstructure:"privateMode"`
EasterEgg *configEasterEgg `mapstructure:"easterEgg"`
Debug bool `mapstructure:"debug"`
MapTiles *configMapTiles `mapstructure:"mapTiles"`
}
type configServer struct {
@ -279,6 +280,13 @@ type configWebmention struct {
DisableReceiving bool `mapstructure:"disableReceiving"`
}
type configMapTiles struct {
Source string `mapstructure:"source"`
Attribution string `mapstructure:"attribution"`
MinZoom int `mapstructure:"minZoom"`
MaxZoom int `mapstructure:"maxZoom"`
}
func (a *goBlog) initConfig(file string) error {
log.Println("Initialize configuration...")
if file != "" {

7
example-config.yml

@ -131,6 +131,13 @@ pathRedirects:
to: "/$1$2"
type: 301 # custom redirect type
# Map tiles
mapTiles:
source: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" # (Optional) URL to use for map tiles
attribution: "&copy; <a href=\"https://www.openstreetmap.org/copyright\">OpenStreetMap</a> contributors" # (Optional) Attribution for map tiles
minZoom: 0 # (Optional) Minimum zoom level
maxZoom: 20 # (Optional) Maximum zoom level
# Blogs
defaultBlog: en # Default blog (needed because you can define multiple blogs)
blogs:

43
geo.go

@ -5,8 +5,6 @@ import (
"fmt"
"io"
"net/http"
"net/http/httptest"
"net/http/httputil"
"net/url"
"strings"
@ -76,44 +74,3 @@ func geoOSMLink(g *gogeouri.Geo) string {
//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) {
targetUrl := *osmUrl
targetUrl.Path = r.URL.Path
proxyRequest, _ := http.NewRequest(http.MethodGet, targetUrl.String(), 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()
}
}

7
geoMap.go

@ -88,8 +88,11 @@ func (a *goBlog) serveGeoMap(w http.ResponseWriter, r *http.Request) {
BlogString: blog,
Canonical: a.getFullAddress(mapPath),
Data: map[string]interface{}{
"locations": locationsJson,
"tracks": tracksJson,
"locations": locationsJson,
"tracks": tracksJson,
"attribution": a.getMapAttribution(),
"minzoom": a.getMinZoom(),
"maxzoom": a.getMaxZoom(),
},
})
}

82
geoTiles.go

@ -0,0 +1,82 @@
package main
import (
"io"
"net/http"
"strings"
"github.com/go-chi/chi/v5"
)
func (a *goBlog) proxyTiles(basePath string) http.HandlerFunc {
tileSource := "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
if c := a.cfg.MapTiles; c != nil && c.Source != "" {
tileSource = c.Source
}
return func(w http.ResponseWriter, r *http.Request) {
// Create a new request to proxy to the tile server
targetUrl := tileSource
targetUrl = strings.ReplaceAll(targetUrl, "{s}", chi.URLParam(r, "s"))
targetUrl = strings.ReplaceAll(targetUrl, "{z}", chi.URLParam(r, "z"))
targetUrl = strings.ReplaceAll(targetUrl, "{x}", chi.URLParam(r, "x"))
targetUrl = strings.ReplaceAll(targetUrl, "{y}", chi.URLParam(r, "y"))
proxyRequest, _ := http.NewRequestWithContext(r.Context(), http.MethodGet, targetUrl, nil)
proxyRequest.Header.Set(userAgent, appUserAgent)
// 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))
}
// Do the request
res, err := a.httpClient.Do(proxyRequest)
if err != nil {
a.serveError(w, r, err.Error(), http.StatusInternalServerError)
return
}
// 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))
}
// Copy result
w.WriteHeader(res.StatusCode)
_, _ = io.Copy(w, res.Body)
_ = res.Body.Close()
}
}
func (a *goBlog) getMinZoom() int {
if c := a.cfg.MapTiles; c != nil {
return c.MinZoom
}
return 0
}
func (a *goBlog) getMaxZoom() int {
if c := a.cfg.MapTiles; c != nil && c.MaxZoom > 0 {
return c.MaxZoom
}
return 20
}
func (a *goBlog) getMapAttribution() string {
if c := a.cfg.MapTiles; c != nil {
return c.Attribution
}
return `&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors`
}

95
geoTiles_test.go

@ -0,0 +1,95 @@
package main
import (
"net/http"
"testing"
"github.com/go-chi/chi/v5"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_proxyTiles(t *testing.T) {
app := &goBlog{
cfg: &config{},
}
hc := &fakeHttpClient{
handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello, World!"))
}),
}
app.httpClient = hc
// Default tile source
m := chi.NewMux()
m.Get("/x/tiles/{s}/{z}/{x}/{y}.png", app.proxyTiles("/x/tiles"))
req, err := http.NewRequest(http.MethodGet, "https://example.org/x/tiles/c/8/134/84.png", nil)
require.NoError(t, err)
resp, err := doHandlerRequest(req, m)
require.NoError(t, err)
assert.Equal(t, http.StatusOK, resp.StatusCode)
assert.Equal(t, "https://c.tile.openstreetmap.org/8/134/84.png", hc.req.URL.String())
// Custom tile source
app.cfg.MapTiles = &configMapTiles{
Source: "https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png",
}
m = chi.NewMux()
m.Get("/x/tiles/{s}/{z}/{x}/{y}.png", app.proxyTiles("/x/tiles"))
req, err = http.NewRequest(http.MethodGet, "https://example.org/x/tiles/c/8/134/84.png", nil)
require.NoError(t, err)
resp, err = doHandlerRequest(req, m)
require.NoError(t, err)
assert.Equal(t, http.StatusOK, resp.StatusCode)
assert.Equal(t, "https://c.tile.opentopomap.org/8/134/84.png", hc.req.URL.String())
}
func Test_getMinZoom(t *testing.T) {
app := &goBlog{
cfg: &config{},
}
assert.Equal(t, 0, app.getMinZoom())
app.cfg.MapTiles = &configMapTiles{
MinZoom: 1,
}
assert.Equal(t, 1, app.getMinZoom())
}
func Test_getMaxZoom(t *testing.T) {
app := &goBlog{
cfg: &config{},
}
assert.Equal(t, 20, app.getMaxZoom())
app.cfg.MapTiles = &configMapTiles{
MaxZoom: 10,
}
assert.Equal(t, 10, app.getMaxZoom())
}
func Test_getMapAttribution(t *testing.T) {
app := &goBlog{
cfg: &config{},
}
assert.Equal(t, `&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors`, app.getMapAttribution())
app.cfg.MapTiles = &configMapTiles{
Attribution: "attribution",
}
assert.Equal(t, "attribution", app.getMapAttribution())
}

33
geoTrack.go

@ -18,14 +18,16 @@ func (p *post) HasTrack() bool {
}
type trackResult struct {
HasPoints bool
Paths [][]*trackPoint
PathsJSON string
Points []*trackPoint
PointsJSON string
Kilometers string
Hours string
Name string
HasPoints bool
Paths [][]*trackPoint
PathsJSON string
Points []*trackPoint
PointsJSON string
Kilometers string
Hours string
Name string
MapAttribution string
MinZoom, MaxZoom int
}
func (a *goBlog) getTrack(p *post) (result *trackResult, err error) {
@ -56,12 +58,15 @@ func (a *goBlog) getTrack(p *post) (result *trackResult, err error) {
}
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,
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,
MapAttribution: a.getMapAttribution(),
MinZoom: a.getMinZoom(),
MaxZoom: a.getMaxZoom(),
}
if parseResult.md != nil {

2
httpRouters.go

@ -99,7 +99,7 @@ func (a *goBlog) mediaFilesRouter(r chi.Router) {
// 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.Get("/tiles/{s}/{z}/{x}/{y}.png", a.proxyTiles("/x/tiles"))
r.With(cacheLoggedIn, a.cacheMiddleware).HandleFunc("/leaflet/*", a.serveFs(leafletFiles, "/x/"))
}

9
templates/assets/js/geomap.js

@ -3,10 +3,13 @@
let locations = (mapEl.dataset.locations == "") ? [] : JSON.parse(mapEl.dataset.locations)
let tracks = (mapEl.dataset.tracks == "") ? [] : JSON.parse(mapEl.dataset.tracks)
let map = L.map('map')
let map = L.map('map', {
minZoom: mapEl.dataset.minzoom,
maxZoom: mapEl.dataset.maxzoom
})
L.tileLayer("/x/tiles/{z}/{x}/{y}.png", {
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
L.tileLayer("/x/tiles/{s}/{z}/{x}/{y}.png", {
attribution: mapEl.dataset.attribution,
}).addTo(map)
let features = []

9
templates/assets/js/geotrack.js

@ -3,10 +3,13 @@
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')
let map = L.map('map', {
minZoom: mapEl.dataset.minzoom,
maxZoom: mapEl.dataset.maxzoom
})
L.tileLayer("/x/tiles/{z}/{x}/{y}.png", {
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
L.tileLayer("/x/tiles/{s}/{z}/{x}/{y}.png", {
attribution: mapEl.dataset.attribution,
}).addTo(map)
let features = []

8
templates/geomap.gohtml

@ -11,7 +11,13 @@
{{ if .Data.nolocations }}
<p>{{ string .Blog.Lang "nolocations" }}</p>
{{ else }}
<div class="p" id="map" data-locations="{{ .Data.locations }}" data-tracks="{{ .Data.tracks }}"></div>
<div class="p" id="map"
data-locations="{{ .Data.locations }}"
data-tracks="{{ .Data.tracks }}"
data-minzoom={{ .Data.minzoom }}
data-maxzoom={{ .Data.maxzoom }}
data-attribution="{{ .Data.attribution }}"
></div>
<script defer src="{{ asset "js/geomap.js" }}"></script>
{{ end }}
</main>

8
templates/trackdetails.gohtml

@ -5,7 +5,13 @@
{{ 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-points="{{ $track.PointsJSON }}"></div>
<div class="p" id="map"
data-paths="{{ $track.PathsJSON }}"
data-points="{{ $track.PointsJSON }}"
data-minzoom={{ $track.MinZoom }}
data-maxzoom={{ $track.MaxZoom }}
data-attribution="{{ $track.MapAttribution }}"
></div>
<script defer src="{{ asset "js/geotrack.js" }}"></script>
{{ end }}
{{ end }}

Loading…
Cancel
Save