summaryrefslogtreecommitdiff
path: root/vendor/github.com/flopp/go-staticmaps/context.go
diff options
context:
space:
mode:
authorFelix Hanley <felix@userspace.com.au>2016-11-21 15:56:46 +0000
committerFelix Hanley <felix@userspace.com.au>2016-11-21 15:56:46 +0000
commit411565dc3c87851017376545383d4afa65d9f833 (patch)
tree44733ff8242c193a95115b27f9e4e88ad3eadde1 /vendor/github.com/flopp/go-staticmaps/context.go
parent98da73fe927ee67b62c1f286b0adb649a20c373c (diff)
downloadcrjw-maps-411565dc3c87851017376545383d4afa65d9f833.tar.gz
crjw-maps-411565dc3c87851017376545383d4afa65d9f833.tar.bz2
Add vendor code
Diffstat (limited to 'vendor/github.com/flopp/go-staticmaps/context.go')
-rw-r--r--vendor/github.com/flopp/go-staticmaps/context.go277
1 files changed, 277 insertions, 0 deletions
diff --git a/vendor/github.com/flopp/go-staticmaps/context.go b/vendor/github.com/flopp/go-staticmaps/context.go
new file mode 100644
index 0000000..3541f44
--- /dev/null
+++ b/vendor/github.com/flopp/go-staticmaps/context.go
@@ -0,0 +1,277 @@
+// Copyright 2016 Florian Pigorsch. All rights reserved.
+//
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+// Package sm (~ static maps) renders static map images from OSM tiles with markers, paths, and filled areas.
+package sm
+
+import (
+ "errors"
+ "image"
+ "image/draw"
+ "math"
+
+ "github.com/fogleman/gg"
+ "github.com/golang/geo/s2"
+)
+
+// Context holds all information about the map image that is to be rendered
+type Context struct {
+ width int
+ height int
+
+ hasZoom bool
+ zoom int
+
+ hasCenter bool
+ center s2.LatLng
+
+ markers []*Marker
+ paths []*Path
+ areas []*Area
+
+ tileProvider *TileProvider
+}
+
+// NewContext creates a new instance of Context
+func NewContext() *Context {
+ t := new(Context)
+ t.width = 512
+ t.height = 512
+ t.hasZoom = false
+ t.hasCenter = false
+ t.tileProvider = NewTileProviderMapQuest()
+ return t
+}
+
+// SetTileProvider sets the TileProvider to be used
+func (m *Context) SetTileProvider(t *TileProvider) {
+ m.tileProvider = t
+}
+
+// SetSize sets the size of the generated image
+func (m *Context) SetSize(width, height int) {
+ m.width = width
+ m.height = height
+}
+
+// SetZoom sets the zoom level
+func (m *Context) SetZoom(zoom int) {
+ m.zoom = zoom
+ m.hasZoom = true
+}
+
+// SetCenter sets the center coordinates
+func (m *Context) SetCenter(center s2.LatLng) {
+ m.center = center
+ m.hasCenter = true
+}
+
+// AddMarker adds a marker to the Context
+func (m *Context) AddMarker(marker *Marker) {
+ m.markers = append(m.markers, marker)
+}
+
+// ClearMarkers removes all markers from the Context
+func (m *Context) ClearMarkers() {
+ m.markers = nil
+}
+
+// AddPath adds a path to the Context
+func (m *Context) AddPath(path *Path) {
+ m.paths = append(m.paths, path)
+}
+
+// ClearPaths removes all paths from the Context
+func (m *Context) ClearPaths() {
+ m.paths = nil
+}
+
+// AddArea adds an area to the Context
+func (m *Context) AddArea(area *Area) {
+ m.areas = append(m.areas, area)
+}
+
+// ClearAreas removes all areas from the Context
+func (m *Context) ClearAreas() {
+ m.areas = nil
+}
+
+func (m *Context) determineBounds() s2.Rect {
+ r := s2.EmptyRect()
+ for _, marker := range m.markers {
+ r = r.Union(marker.bounds())
+ }
+ for _, path := range m.paths {
+ r = r.Union(path.bounds())
+ }
+ for _, area := range m.areas {
+ r = r.Union(area.bounds())
+ }
+ return r
+}
+
+func (m *Context) determineExtraMarginPixels() float64 {
+ p := 0.0
+ for _, marker := range m.markers {
+ if pp := marker.extraMarginPixels(); pp > p {
+ p = pp
+ }
+ }
+ for _, path := range m.paths {
+ if pp := path.extraMarginPixels(); pp > p {
+ p = pp
+ }
+ }
+ for _, area := range m.areas {
+ if pp := area.extraMarginPixels(); pp > p {
+ p = pp
+ }
+ }
+ return p
+}
+
+func (m *Context) determineZoom(bounds s2.Rect, center s2.LatLng) int {
+ b := bounds.AddPoint(center)
+ if b.IsEmpty() || b.IsPoint() {
+ return 15
+ }
+
+ tileSize := m.tileProvider.TileSize
+ margin := 4.0 + m.determineExtraMarginPixels()
+ w := (float64(m.width) - 2.0*margin) / float64(tileSize)
+ h := (float64(m.height) - 2.0*margin) / float64(tileSize)
+ minX := (b.Lo().Lng.Degrees() + 180.0) / 360.0
+ maxX := (b.Hi().Lng.Degrees() + 180.0) / 360.0
+ minY := (1.0 - math.Log(math.Tan(b.Lo().Lat.Radians())+(1.0/math.Cos(b.Lo().Lat.Radians())))/math.Pi) / 2.0
+ maxY := (1.0 - math.Log(math.Tan(b.Hi().Lat.Radians())+(1.0/math.Cos(b.Hi().Lat.Radians())))/math.Pi) / 2.0
+ dx := math.Abs(maxX - minX)
+ dy := math.Abs(maxY - minY)
+
+ zoom := 1
+ for zoom < 30 {
+ tiles := float64(uint(1) << uint(zoom))
+ if dx*tiles > w || dy*tiles > h {
+ return zoom - 1
+ }
+ zoom = zoom + 1
+ }
+
+ return 15
+}
+
+type transformer struct {
+ zoom int
+ tileSize int
+ pWidth, pHeight int
+ pCenterX, pCenterY int
+ tCountX, tCountY int
+ tCenterX, tCenterY float64
+ tOriginX, tOriginY int
+}
+
+func newTransformer(width int, height int, zoom int, llCenter s2.LatLng, tileSize int) *transformer {
+ t := new(transformer)
+ t.zoom = zoom
+ t.tileSize = tileSize
+ t.tCenterX, t.tCenterY = t.ll2t(llCenter)
+
+ ww := float64(width) / float64(tileSize)
+ hh := float64(height) / float64(tileSize)
+
+ t.tOriginX = int(math.Floor(t.tCenterX - 0.5*ww))
+ t.tOriginY = int(math.Floor(t.tCenterY - 0.5*hh))
+
+ t.tCountX = 1 + int(math.Floor(t.tCenterX+0.5*ww)) - t.tOriginX
+ t.tCountY = 1 + int(math.Floor(t.tCenterY+0.5*hh)) - t.tOriginY
+
+ t.pWidth = t.tCountX * tileSize
+ t.pHeight = t.tCountY * tileSize
+
+ t.pCenterX = int((t.tCenterX - float64(t.tOriginX)) * float64(tileSize))
+ t.pCenterY = int((t.tCenterY - float64(t.tOriginY)) * float64(tileSize))
+
+ return t
+}
+
+func (t *transformer) ll2t(ll s2.LatLng) (float64, float64) {
+ tiles := math.Exp2(float64(t.zoom))
+ x := tiles * (ll.Lng.Degrees() + 180.0) / 360.0
+ y := tiles * (1 - math.Log(math.Tan(ll.Lat.Radians())+(1.0/math.Cos(ll.Lat.Radians())))/math.Pi) / 2.0
+ return x, y
+}
+
+func (t *transformer) ll2p(ll s2.LatLng) (float64, float64) {
+ x, y := t.ll2t(ll)
+ x = float64(t.pCenterX) + (x-t.tCenterX)*float64(t.tileSize)
+ y = float64(t.pCenterY) + (y-t.tCenterY)*float64(t.tileSize)
+ return x, y
+}
+
+// Render actually renders the map image including all map objects (markers, paths, areas)
+func (m *Context) Render() (image.Image, error) {
+ bounds := m.determineBounds()
+
+ center := m.center
+ if !m.hasCenter {
+ if bounds.IsEmpty() {
+ return nil, errors.New("No center coordinates specified, cannot determine center from markers")
+ }
+ center = bounds.Center()
+ }
+
+ zoom := m.zoom
+ if !m.hasZoom {
+ zoom = m.determineZoom(bounds, center)
+ }
+
+ tileSize := m.tileProvider.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)
+
+ // fetch and draw tiles to img
+ t := NewTileFetcher(m.tileProvider)
+ for xx := 0; xx < trans.tCountX; xx++ {
+ x := trans.tOriginX + xx
+ if x < 0 {
+ x = x + (1 << uint(zoom))
+ }
+ for yy := 0; yy < trans.tCountY; yy++ {
+ y := trans.tOriginY + yy
+ if tileImg, err := t.Fetch(zoom, x, y); err == nil {
+ gc.DrawImage(tileImg, xx*tileSize, yy*tileSize)
+ }
+ }
+ }
+
+ // draw map objects
+ for _, area := range m.areas {
+ area.draw(gc, trans)
+ }
+ for _, path := range m.paths {
+ path.draw(gc, trans)
+ }
+ for _, marker := range m.markers {
+ marker.draw(gc, trans)
+ }
+
+ // crop image
+ croppedImg := image.NewRGBA(image.Rect(0, 0, int(m.width), int(m.height)))
+ draw.Draw(croppedImg, image.Rect(0, 0, int(m.width), int(m.height)),
+ img, image.Point{trans.pCenterX - int(m.width)/2, trans.pCenterY - int(m.height)/2},
+ draw.Src)
+
+ // draw attribution
+ _, textHeight := gc.MeasureString(m.tileProvider.Attribution)
+ boxHeight := textHeight + 4.0
+ gc = gg.NewContextForRGBA(croppedImg)
+ gc.SetRGBA(0.0, 0.0, 0.0, 0.5)
+ gc.DrawRectangle(0.0, float64(m.height)-boxHeight, float64(m.width), 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)
+
+ return croppedImg, nil
+}