mirror of https://github.com/jlelse/GoBlog
parent
bf3074a54d
commit
783afc1c3e
|
@ -26,6 +26,7 @@ type config struct {
|
||||||
PrivateMode *configPrivateMode `mapstructure:"privateMode"`
|
PrivateMode *configPrivateMode `mapstructure:"privateMode"`
|
||||||
EasterEgg *configEasterEgg `mapstructure:"easterEgg"`
|
EasterEgg *configEasterEgg `mapstructure:"easterEgg"`
|
||||||
Debug bool `mapstructure:"debug"`
|
Debug bool `mapstructure:"debug"`
|
||||||
|
MapTiles *configMapTiles `mapstructure:"mapTiles"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type configServer struct {
|
type configServer struct {
|
||||||
|
@ -279,6 +280,13 @@ type configWebmention struct {
|
||||||
DisableReceiving bool `mapstructure:"disableReceiving"`
|
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 {
|
func (a *goBlog) initConfig(file string) error {
|
||||||
log.Println("Initialize configuration...")
|
log.Println("Initialize configuration...")
|
||||||
if file != "" {
|
if file != "" {
|
||||||
|
|
|
@ -131,6 +131,13 @@ pathRedirects:
|
||||||
to: "/$1$2"
|
to: "/$1$2"
|
||||||
type: 301 # custom redirect type
|
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: "© <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
|
# Blogs
|
||||||
defaultBlog: en # Default blog (needed because you can define multiple blogs)
|
defaultBlog: en # Default blog (needed because you can define multiple blogs)
|
||||||
blogs:
|
blogs:
|
||||||
|
|
43
geo.go
43
geo.go
|
@ -5,8 +5,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
|
||||||
"net/http/httputil"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
@ -76,44 +74,3 @@ func geoOSMLink(g *gogeouri.Geo) string {
|
||||||
|
|
||||||
//go:embed leaflet/*
|
//go:embed leaflet/*
|
||||||
var leafletFiles embed.FS
|
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -88,8 +88,11 @@ func (a *goBlog) serveGeoMap(w http.ResponseWriter, r *http.Request) {
|
||||||
BlogString: blog,
|
BlogString: blog,
|
||||||
Canonical: a.getFullAddress(mapPath),
|
Canonical: a.getFullAddress(mapPath),
|
||||||
Data: map[string]interface{}{
|
Data: map[string]interface{}{
|
||||||
"locations": locationsJson,
|
"locations": locationsJson,
|
||||||
"tracks": tracksJson,
|
"tracks": tracksJson,
|
||||||
|
"attribution": a.getMapAttribution(),
|
||||||
|
"minzoom": a.getMinZoom(),
|
||||||
|
"maxzoom": a.getMaxZoom(),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 `© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors`
|
||||||
|
}
|
|
@ -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, `© <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
33
geoTrack.go
|
@ -18,14 +18,16 @@ func (p *post) HasTrack() bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
type trackResult struct {
|
type trackResult struct {
|
||||||
HasPoints bool
|
HasPoints bool
|
||||||
Paths [][]*trackPoint
|
Paths [][]*trackPoint
|
||||||
PathsJSON string
|
PathsJSON string
|
||||||
Points []*trackPoint
|
Points []*trackPoint
|
||||||
PointsJSON string
|
PointsJSON string
|
||||||
Kilometers string
|
Kilometers string
|
||||||
Hours string
|
Hours string
|
||||||
Name string
|
Name string
|
||||||
|
MapAttribution string
|
||||||
|
MinZoom, MaxZoom int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *goBlog) getTrack(p *post) (result *trackResult, err error) {
|
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{
|
result = &trackResult{
|
||||||
HasPoints: len(parseResult.paths) > 0 && len(parseResult.paths[0]) > 0,
|
HasPoints: len(parseResult.paths) > 0 && len(parseResult.paths[0]) > 0,
|
||||||
Paths: parseResult.paths,
|
Paths: parseResult.paths,
|
||||||
PathsJSON: string(pathsJSON),
|
PathsJSON: string(pathsJSON),
|
||||||
Points: parseResult.points,
|
Points: parseResult.points,
|
||||||
PointsJSON: string(pointsJSON),
|
PointsJSON: string(pointsJSON),
|
||||||
Name: parseResult.gpxData.Name,
|
Name: parseResult.gpxData.Name,
|
||||||
|
MapAttribution: a.getMapAttribution(),
|
||||||
|
MinZoom: a.getMinZoom(),
|
||||||
|
MaxZoom: a.getMaxZoom(),
|
||||||
}
|
}
|
||||||
|
|
||||||
if parseResult.md != nil {
|
if parseResult.md != nil {
|
||||||
|
|
|
@ -99,7 +99,7 @@ func (a *goBlog) mediaFilesRouter(r chi.Router) {
|
||||||
// Various other routes
|
// Various other routes
|
||||||
func (a *goBlog) xRouter(r chi.Router) {
|
func (a *goBlog) xRouter(r chi.Router) {
|
||||||
r.Use(a.privateModeHandler)
|
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/"))
|
r.With(cacheLoggedIn, a.cacheMiddleware).HandleFunc("/leaflet/*", a.serveFs(leafletFiles, "/x/"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,10 +3,13 @@
|
||||||
let locations = (mapEl.dataset.locations == "") ? [] : JSON.parse(mapEl.dataset.locations)
|
let locations = (mapEl.dataset.locations == "") ? [] : JSON.parse(mapEl.dataset.locations)
|
||||||
let tracks = (mapEl.dataset.tracks == "") ? [] : JSON.parse(mapEl.dataset.tracks)
|
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", {
|
L.tileLayer("/x/tiles/{s}/{z}/{x}/{y}.png", {
|
||||||
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
attribution: mapEl.dataset.attribution,
|
||||||
}).addTo(map)
|
}).addTo(map)
|
||||||
|
|
||||||
let features = []
|
let features = []
|
||||||
|
|
|
@ -3,10 +3,13 @@
|
||||||
let paths = (mapEl.dataset.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 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", {
|
L.tileLayer("/x/tiles/{s}/{z}/{x}/{y}.png", {
|
||||||
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
attribution: mapEl.dataset.attribution,
|
||||||
}).addTo(map)
|
}).addTo(map)
|
||||||
|
|
||||||
let features = []
|
let features = []
|
||||||
|
|
|
@ -11,7 +11,13 @@
|
||||||
{{ if .Data.nolocations }}
|
{{ if .Data.nolocations }}
|
||||||
<p>{{ string .Blog.Lang "nolocations" }}</p>
|
<p>{{ string .Blog.Lang "nolocations" }}</p>
|
||||||
{{ else }}
|
{{ 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>
|
<script defer src="{{ asset "js/geomap.js" }}"></script>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</main>
|
</main>
|
||||||
|
|
|
@ -5,7 +5,13 @@
|
||||||
{{ if $track.HasPoints }}
|
{{ if $track.HasPoints }}
|
||||||
{{ $lang := .Blog.Lang }}
|
{{ $lang := .Blog.Lang }}
|
||||||
<p>{{ with $track.Name }}<b>{{ . }}</b> {{ end }}{{ with $track.Kilometers }}🏁 {{ . }} {{ string $lang "kilometers" }} {{ end }}{{ with $track.Hours }}⌛ {{ . }}{{ end }}</p>
|
<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>
|
<script defer src="{{ asset "js/geotrack.js" }}"></script>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
Loading…
Reference in New Issue