Skip to content

Commit

Permalink
feat: allow static map as tile provider
Browse files Browse the repository at this point in the history
- make TileProvider interface
- add StaticMapProvider implementation
- add static google map example
  • Loading branch information
galihrivanto committed Feb 3, 2023
1 parent c226716 commit 99e44d8
Show file tree
Hide file tree
Showing 5 changed files with 503 additions and 109 deletions.
79 changes: 60 additions & 19 deletions context.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,11 @@ type Context struct {
background color.Color

objects []MapObject
overlays []*TileProvider
overlays []TileProvider

userAgent string
online bool
tileProvider *TileProvider
tileProvider TileProvider
cache TileCache

overrideAttribution *string
Expand All @@ -65,7 +65,7 @@ func NewContext() *Context {
}

// SetTileProvider sets the TileProvider to be used
func (m *Context) SetTileProvider(t *TileProvider) {
func (m *Context) SetTileProvider(t TileProvider) {
m.tileProvider = t
}

Expand Down Expand Up @@ -209,7 +209,7 @@ func (m *Context) ClearObjects() {
}

// AddOverlay adds an overlay to the Context
func (m *Context) AddOverlay(overlay *TileProvider) {
func (m *Context) AddOverlay(overlay TileProvider) {
m.overlays = append(m.overlays, overlay)
}

Expand All @@ -233,7 +233,17 @@ func (m *Context) Attribution() string {
if m.overrideAttribution != nil {
return *m.overrideAttribution
}
return m.tileProvider.Attribution
return m.tileProvider.Attribution()
}

func (m *Context) tileSize() int {
// if layer is tile provider
tileSize := 256
if p, ok := m.tileProvider.(MapTileProvider); ok {
tileSize = p.TileSize()
}

return tileSize
}

func (m *Context) determineBounds() s2.Rect {
Expand Down Expand Up @@ -268,7 +278,8 @@ func (m *Context) determineZoom(bounds s2.Rect, center s2.LatLng) int {
return 15
}

tileSize := m.tileProvider.TileSize
tileSize := m.tileSize()

marginL, marginT, marginR, marginB := m.determineExtraMarginPixels()
w := (float64(m.width) - marginL - marginR) / float64(tileSize)
h := (float64(m.height) - marginT - marginB) / float64(tileSize)
Expand Down Expand Up @@ -320,7 +331,7 @@ func (m *Context) adjustCenter(center s2.LatLng, zoom int) s2.LatLng {
return center
}

transformer := newTransformer(m.width, m.height, zoom, center, m.tileProvider.TileSize)
transformer := newTransformer(m.width, m.height, zoom, center, m.tileSize())

first := true
minX := 0.0
Expand Down Expand Up @@ -404,7 +415,7 @@ func (m *Context) Transformer() (*Transformer, error) {
return nil, err
}

return newTransformer(m.width, m.height, zoom, center, m.tileProvider.TileSize), nil
return newTransformer(m.width, m.height, zoom, center, m.tileSize()), nil
}

func newTransformer(width int, height int, zoom int, llCenter s2.LatLng, tileSize int) *Transformer {
Expand Down Expand Up @@ -498,7 +509,7 @@ func (m *Context) Render() (image.Image, error) {
return nil, err
}

tileSize := m.tileProvider.TileSize
tileSize := m.tileSize()
trans := newTransformer(m.width, m.height, zoom, center, tileSize)
img := image.NewRGBA(image.Rect(0, 0, trans.pWidth, trans.pHeight))
gc := gg.NewContextForRGBA(img)
Expand All @@ -507,13 +518,13 @@ func (m *Context) Render() (image.Image, error) {
}

// fetch and draw tiles to img
layers := []*TileProvider{m.tileProvider}
layers := []TileProvider{m.tileProvider}
if m.overlays != nil {
layers = append(layers, m.overlays...)
}

for _, layer := range layers {
if err := m.renderLayer(gc, zoom, trans, tileSize, layer); err != nil {
if err := m.renderLayer(gc, zoom, center, trans, tileSize, layer); err != nil {
return nil, err
}
}
Expand Down Expand Up @@ -557,7 +568,7 @@ func (m *Context) RenderWithTransformer() (image.Image, *Transformer, error) {
return nil, nil, err
}

tileSize := m.tileProvider.TileSize
tileSize := m.tileSize()
trans := newTransformer(m.width, m.height, zoom, center, tileSize)
img := image.NewRGBA(image.Rect(0, 0, trans.pWidth, trans.pHeight))
gc := gg.NewContextForRGBA(img)
Expand All @@ -566,13 +577,13 @@ func (m *Context) RenderWithTransformer() (image.Image, *Transformer, error) {
}

// fetch and draw tiles to img
layers := []*TileProvider{m.tileProvider}
layers := []TileProvider{m.tileProvider}
if m.overlays != nil {
layers = append(layers, m.overlays...)
}

for _, layer := range layers {
if err := m.renderLayer(gc, zoom, trans, tileSize, layer); err != nil {
if err := m.renderLayer(gc, zoom, center, trans, tileSize, layer); err != nil {
return nil, nil, err
}
}
Expand All @@ -583,16 +594,16 @@ func (m *Context) RenderWithTransformer() (image.Image, *Transformer, error) {
}

// draw attribution
if m.tileProvider.Attribution == "" {
if m.tileProvider.Attribution() == "" {
return img, trans, nil
}
_, textHeight := gc.MeasureString(m.tileProvider.Attribution)
_, textHeight := gc.MeasureString(m.tileProvider.Attribution())
boxHeight := textHeight + 4.0
gc.SetRGBA(0.0, 0.0, 0.0, 0.5)
gc.DrawRectangle(0.0, float64(trans.pHeight)-boxHeight, float64(trans.pWidth), boxHeight)
gc.Fill()
gc.SetRGBA(1.0, 1.0, 1.0, 0.75)
gc.DrawString(m.tileProvider.Attribution, 4.0, float64(m.height)-4.0)
gc.DrawString(m.tileProvider.Attribution(), 4.0, float64(m.height)-4.0)

return img, trans, nil
}
Expand All @@ -611,7 +622,7 @@ func (m *Context) RenderWithBounds() (image.Image, s2.Rect, error) {
return img, trans.Rect(), nil
}

func (m *Context) renderLayer(gc *gg.Context, zoom int, trans *Transformer, tileSize int, provider *TileProvider) error {
func (m *Context) renderTileLayer(gc *gg.Context, zoom int, trans *Transformer, tileSize int, provider MapTileProvider) error {
var wg sync.WaitGroup
tiles := (1 << uint(zoom))
fetchedTiles := make(chan *Tile)
Expand Down Expand Up @@ -646,7 +657,7 @@ func (m *Context) renderLayer(gc *gg.Context, zoom int, trans *Transformer, tile
tile.X = xx * tileSize
tile.Y = yy * tileSize
fetchedTiles <- tile
} else if err == errTileNotFound && provider.IgnoreNotFound {
} else if err == errTileNotFound && provider.IgnoreNotFound() {
log.Printf("Error downloading tile file: %s (Ignored)", err)
} else {
log.Printf("Error downloading tile file: %s", err)
Expand All @@ -664,3 +675,33 @@ func (m *Context) renderLayer(gc *gg.Context, zoom int, trans *Transformer, tile

return nil
}

func (m *Context) renderStaticMap(gc *gg.Context, zoom int, center s2.LatLng, trans *Transformer, provider StaticMapProvider) error {
t := NewStaticMapFetcher(provider, m.cache, m.online)
if m.userAgent != "" {
t.SetUserAgent(m.userAgent)
}

mm := &StaticMap{Zoom: zoom, X: center.Lng.Degrees(), Y: center.Lat.Degrees(), Width: trans.pWidth, Height: trans.pHeight}
err := t.Fetch(mm)
if err != nil {
log.Printf("Error downloading static map file: %s", err)
}

gc.DrawImage(mm.Img, 0, 0)

return err
}

func (m *Context) renderLayer(gc *gg.Context, zoom int, center s2.LatLng, trans *Transformer, tileSize int, provider TileProvider) error {
// if provider is TileProvider
if tileProvider, ok := provider.(MapTileProvider); ok {
log.Println("render tile")
return m.renderTileLayer(gc, zoom, trans, tileSize, tileProvider)
} else if staticMapProvider, ok := provider.(StaticMapProvider); ok {
log.Println("render static map")
return m.renderStaticMap(gc, zoom, center, trans, staticMapProvider)
}

return nil
}
123 changes: 123 additions & 0 deletions examples/google-static-map/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package main

import (
"fmt"
"os"

sm "github.com/flopp/go-staticmaps"
"github.com/fogleman/gg"
"github.com/golang/geo/s2"
)

const googleStaticMapURL = "https://maps.googleapis.com/maps/api/staticmap?"

// GMapOption .
type GMapOption func(*GMapOptions)

// GMapOptions .
type GMapOptions struct {
clientID string
signature string
key string
styles []string
}

// GoogleClientID .
func GMapClientID(clientID, signature string) GMapOption {
return func(gmo *GMapOptions) {
gmo.clientID = clientID
gmo.signature = signature
}
}

// GMapKey .
func GMapKey(key string) GMapOption {
return func(gmo *GMapOptions) {
gmo.key = key
}
}

// GMapStyles .
func GMapStyles(styles ...string) GMapOption {
return func(gmo *GMapOptions) {
gmo.styles = append(gmo.styles, styles...)
}
}

func GMapTileProvider(options ...GMapOption) sm.TileProvider {
// default option
opt := &GMapOptions{
styles: make([]string, 0),
}

for _, option := range options {
option(opt)
}

t := new(gMapProvider)
t.name = "google-map"
t.attribution = "Google Map (inc)"
t.options = opt

return t
}

// gMapProvider .
type gMapProvider struct {
name string
attribution string
options *GMapOptions
}

func (p *gMapProvider) Name() string {
return p.name
}

func (p *gMapProvider) Attribution() string {
return p.attribution
}

func (p *gMapProvider) GetURL(zoom int, x, y float64, width, height int) string {
// construct google static map url
var url string
if p.options.key != "" {
url = googleStaticMapURL + "key=" + p.options.key
} else {
url = googleStaticMapURL + "client-id=" + p.options.clientID + "&signature=" + p.options.signature
}

if width > 0 && height > 0 {
url += fmt.Sprintf("&size=%dx%d", width, height)
}

if x > 0 && y > 0 {
url += fmt.Sprintf("&center=%f,%f", y, x)
}

if zoom > 0 {
url += fmt.Sprintf("&zoom=%d", zoom)
}

return url
}

func main() {
ctx := sm.NewContext()
ctx.SetSize(400, 300)
ctx.SetCenter(s2.LatLngFromDegrees(1.3011624468555132, 103.85775516239742))
ctx.SetZoom(17)
ctx.SetTileProvider(
GMapTileProvider(
GMapKey(os.Getenv("GOOGLE_MAP_KEY")),
),
)

img, err := ctx.Render()
if err != nil {
panic(err)
}

if err := gg.SavePNG("google-map.png", img); err != nil {
panic(err)
}
}
Loading

0 comments on commit 99e44d8

Please sign in to comment.