1
0
Fork 0
mirror of https://github.com/Luzifer/staticmap.git synced 2024-12-30 01:31:18 +00:00

Add support for overlay map generation with post request

Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
Knut Ahlers 2018-06-17 16:26:57 +02:00
parent a37dafe18d
commit 491effdd99
Signed by: luzifer
GPG key ID: DC2729FDD34BE99E
5 changed files with 182 additions and 1 deletions

View file

@ -33,6 +33,14 @@ The map center is set to a coordinate within Hamburg, Germany with a zoom level
![](example/map.png) ![](example/map.png)
### Overlay support
Starting with version `v0.4.0` the generator supports map overlays for tiles based on transparent background (like [OpenFireMap](http://openfiremap.org/), [OpenSeaMap](http://www.openseamap.org/), ...). As those requests would be too complex for `GET` requests and are also not a common usecase they are implemented as `POST` requests containing a JSON object.
This example is generated with [OpenFireMap](http://openfiremap.org/) overlay tiles using the [`example/postmap.json`](example/postmap.json) file:
![](example/postmap.png)
## Setup ## Setup
- There is a Docker container available: [`luzifer/staticmap`](https://hub.docker.com/r/luzifer/staticmap/) - There is a Docker container available: [`luzifer/staticmap`](https://hub.docker.com/r/luzifer/staticmap/)

28
example/postmap.json Normal file
View file

@ -0,0 +1,28 @@
{
"center": {
"lat": 53.5438,
"lon": 9.9768
},
"height": 500,
"markers": [
{
"color": "blue",
"coord": {
"lat": 53.54129165,
"lon": 9.98420576699353
}
},
{
"color": "yellow",
"coord": {
"lat": 53.54565525,
"lon": 9.9680555636958
}
}
],
"overlays": [
"http://openfiremap.org/hytiles/{0}/{1}/{2}.png"
],
"width": 800,
"zoom": 15
}

BIN
example/postmap.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 416 KiB

33
main.go
View file

@ -1,6 +1,7 @@
package main package main
import ( import (
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"io" "io"
@ -59,7 +60,8 @@ func main() {
r := mux.NewRouter() r := mux.NewRouter()
r.HandleFunc("/status", func(res http.ResponseWriter, r *http.Request) { http.Error(res, "I'm fine", http.StatusOK) }) r.HandleFunc("/status", func(res http.ResponseWriter, r *http.Request) { http.Error(res, "I'm fine", http.StatusOK) })
r.Handle("/map.png", tollbooth.LimitFuncHandler(rateLimit, handleMapRequest)) r.Handle("/map.png", tollbooth.LimitFuncHandler(rateLimit, handleMapRequest)).Methods("GET")
r.Handle("/map.png", tollbooth.LimitFuncHandler(rateLimit, handlePostMapRequest)).Methods("POST")
log.Fatalf("HTTP Server exitted: %s", http.ListenAndServe(cfg.Listen, httpHelper.NewHTTPLogHandler(r))) log.Fatalf("HTTP Server exitted: %s", http.ListenAndServe(cfg.Listen, httpHelper.NewHTTPLogHandler(r)))
} }
@ -104,6 +106,35 @@ func handleMapRequest(res http.ResponseWriter, r *http.Request) {
io.Copy(res, mapReader) io.Copy(res, mapReader)
} }
func handlePostMapRequest(res http.ResponseWriter, r *http.Request) {
var (
body = postMapEnvelope{}
mapReader io.ReadCloser
)
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
http.Error(res, fmt.Sprintf("Unable to parse input: %s", err), http.StatusBadRequest)
return
}
opts, err := body.toGenerateMapConfig()
if err != nil {
http.Error(res, fmt.Sprintf("Unable to process input: %s", err), http.StatusBadRequest)
return
}
if mapReader, err = cacheFunc(opts); err != nil {
log.Errorf("Map render failed: %s (Request: %s)", err, r.URL.String())
http.Error(res, fmt.Sprintf("I experienced difficulties rendering your map: %s", err), http.StatusInternalServerError)
return
}
defer mapReader.Close()
res.Header().Set("Content-Type", "image/png")
res.Header().Set("Cache-Control", "public")
io.Copy(res, mapReader)
}
func parseCoordinate(coord string) (s2.LatLng, error) { func parseCoordinate(coord string) (s2.LatLng, error) {
if coord == "" { if coord == "" {
return s2.LatLng{}, errors.New("No coordinate given") return s2.LatLng{}, errors.New("No coordinate given")

114
postmap.go Normal file
View file

@ -0,0 +1,114 @@
package main
import (
"crypto/sha256"
"fmt"
"strings"
staticMap "github.com/Luzifer/go-staticmaps"
"github.com/golang/geo/s2"
)
type postMapEnvelope struct {
Center postMapPoint `json:"center"`
Zoom int `json:"zoom"`
Markers postMapMarkers `json:"markers"`
Width int `json:"width"`
Height int `json:"height"`
DisableAttribution bool `json:"disable_attribution"`
Overlays postMapOverlay `json:"overlays"`
}
func (p postMapEnvelope) toGenerateMapConfig() (generateMapConfig, error) {
result := generateMapConfig{
Center: p.Center.getPoint(),
Zoom: p.Zoom,
Width: p.Width,
Height: p.Height,
DisableAttribution: p.DisableAttribution,
}
if p.Width > mapMaxX || p.Height > mapMaxY {
return generateMapConfig{}, fmt.Errorf("Map size exceeds allowed bounds of %dx%d", mapMaxX, mapMaxY)
}
var err error
if result.Markers, err = p.Markers.toMarkers(); err != nil {
return generateMapConfig{}, err
}
if result.Overlays, err = p.Overlays.toOverlays(); err != nil {
return generateMapConfig{}, err
}
return result, nil
}
type postMapMarker struct {
Size string `json:"size"`
Color string `json:"color"`
Coord postMapPoint `json:"coord"`
}
func (p postMapMarker) String() string {
parts := []string{}
if p.Size != "" {
parts = append(parts, fmt.Sprintf("size:%s", p.Size))
}
if p.Color != "" {
parts = append(parts, fmt.Sprintf("color:%s", p.Color))
}
parts = append(parts, p.Coord.String())
return strings.Join(parts, "|")
}
type postMapMarkers []postMapMarker
func (p postMapMarkers) toMarkers() ([]marker, error) {
raw := []string{}
for _, pm := range p {
raw = append(raw, pm.String())
}
return parseMarkerLocations(raw)
}
type postMapPoint struct {
Lat float64 `json:"lat"`
Lon float64 `json:"lon"`
}
func (p postMapPoint) String() string {
return fmt.Sprintf("%f,%f", p.Lat, p.Lon)
}
func (p postMapPoint) getPoint() s2.LatLng {
return s2.LatLngFromDegrees(p.Lat, p.Lon)
}
type postMapOverlay []string
func (p postMapOverlay) toOverlays() ([]*staticMap.TileProvider, error) {
result := []*staticMap.TileProvider{}
for _, pat := range p {
for _, v := range []string{`{0}`, `{1}`, `{2}`} {
if !strings.Contains(pat, v) {
return nil, fmt.Errorf("Placeholder %q not found in pattern %q", v, pat)
}
}
pat = strings.NewReplacer(`{0}`, `%[2]d`, `{1}`, `%[3]d`, `{2}`, `%[4]d`).Replace(pat)
result = append(result, &staticMap.TileProvider{
Name: fmt.Sprintf("%x", sha256.Sum256([]byte(pat))),
TileSize: 256,
URLPattern: pat,
})
}
return result, nil
}