diff options
| author | Felix Hanley <felix@userspace.com.au> | 2016-11-21 15:56:46 +0000 |
|---|---|---|
| committer | Felix Hanley <felix@userspace.com.au> | 2016-11-21 15:56:46 +0000 |
| commit | 411565dc3c87851017376545383d4afa65d9f833 (patch) | |
| tree | 44733ff8242c193a95115b27f9e4e88ad3eadde1 /vendor/github.com/flopp/go-staticmaps/context.go | |
| parent | 98da73fe927ee67b62c1f286b0adb649a20c373c (diff) | |
| download | crjw-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.go | 277 |
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 +} |
