From 411565dc3c87851017376545383d4afa65d9f833 Mon Sep 17 00:00:00 2001 From: Felix Hanley Date: Mon, 21 Nov 2016 22:56:46 +0700 Subject: Add vendor code --- vendor/github.com/flopp/go-coordsparser/LICENSE | 21 ++ .../flopp/go-coordsparser/coordsparser.go | 172 +++++++++++++ vendor/github.com/flopp/go-staticmaps/LICENSE | 21 ++ vendor/github.com/flopp/go-staticmaps/area.go | 94 +++++++ vendor/github.com/flopp/go-staticmaps/color.go | 65 +++++ vendor/github.com/flopp/go-staticmaps/context.go | 277 +++++++++++++++++++++ .../create-static-map/create-static-map.go | 134 ++++++++++ .../github.com/flopp/go-staticmaps/map_object.go | 13 + vendor/github.com/flopp/go-staticmaps/marker.go | 125 ++++++++++ vendor/github.com/flopp/go-staticmaps/path.go | 103 ++++++++ .../github.com/flopp/go-staticmaps/tile_fetcher.go | 153 ++++++++++++ .../flopp/go-staticmaps/tile_provider.go | 98 ++++++++ vendor/github.com/flopp/go-staticmaps/util.go | 18 ++ 13 files changed, 1294 insertions(+) create mode 100644 vendor/github.com/flopp/go-coordsparser/LICENSE create mode 100644 vendor/github.com/flopp/go-coordsparser/coordsparser.go create mode 100644 vendor/github.com/flopp/go-staticmaps/LICENSE create mode 100644 vendor/github.com/flopp/go-staticmaps/area.go create mode 100644 vendor/github.com/flopp/go-staticmaps/color.go create mode 100644 vendor/github.com/flopp/go-staticmaps/context.go create mode 100644 vendor/github.com/flopp/go-staticmaps/create-static-map/create-static-map.go create mode 100644 vendor/github.com/flopp/go-staticmaps/map_object.go create mode 100644 vendor/github.com/flopp/go-staticmaps/marker.go create mode 100644 vendor/github.com/flopp/go-staticmaps/path.go create mode 100644 vendor/github.com/flopp/go-staticmaps/tile_fetcher.go create mode 100644 vendor/github.com/flopp/go-staticmaps/tile_provider.go create mode 100644 vendor/github.com/flopp/go-staticmaps/util.go (limited to 'vendor/github.com/flopp') diff --git a/vendor/github.com/flopp/go-coordsparser/LICENSE b/vendor/github.com/flopp/go-coordsparser/LICENSE new file mode 100644 index 0000000..7417b3d --- /dev/null +++ b/vendor/github.com/flopp/go-coordsparser/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Florian Pigorsch + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/flopp/go-coordsparser/coordsparser.go b/vendor/github.com/flopp/go-coordsparser/coordsparser.go new file mode 100644 index 0000000..6836892 --- /dev/null +++ b/vendor/github.com/flopp/go-coordsparser/coordsparser.go @@ -0,0 +1,172 @@ +// 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 coordsparser is a library for parsing (geographic) coordinates in various string formats +package coordsparser + +import ( + "fmt" + "regexp" + "strconv" +) + +// Parse parses a coordinate string and returns a lat/lng pair or an error +func Parse(s string) (float64, float64, error) { + lat, lng, err := ParseD(s) + if err == nil { + return lat, lng, nil + } + + lat, lng, err = ParseHD(s) + if err == nil { + return lat, lng, nil + } + + lat, lng, err = ParseHDM(s) + if err == nil { + return lat, lng, nil + } + + lat, lng, err = ParseHDMS(s) + if err == nil { + return lat, lng, nil + } + + return 0, 0, fmt.Errorf("Cannot parse coordinates: %s", s) +} + +// ParseD parses a coordinate string of the form "D.DDDD D.DDDD" and returns a lat/lng pair or an error +func ParseD(s string) (float64, float64, error) { + re := regexp.MustCompile(`^\s*([+-]?[\d\.]+)\s*(,|;|:|\s)\s*([+-]?[\d\.]+)\s*$`) + + matches := re.FindStringSubmatch(s) + if matches == nil { + return 0, 0, fmt.Errorf("Cannot parse 'D' string: %s", s) + } + + lat, err := strconv.ParseFloat(matches[1], 64) + if err != nil || lat < -90 || lat > 90 { + return 0, 0, fmt.Errorf("Cannot parse 'D' string: %s", s) + } + + lng, err := strconv.ParseFloat(matches[3], 64) + if err != nil || lng < -180 || lng > 180 { + return 0, 0, fmt.Errorf("Cannot parse 'D' string: %s", s) + } + + return lat, lng, nil +} + +// ParseHD parses a coordinate string of the form "H D.DDDD H D.DDDD" and returns a lat/lng pair or an error +func ParseHD(s string) (float64, float64, error) { + re := regexp.MustCompile(`^\s*([NnSs])\s*([\d\.]+)\s+([EeWw])\s*([\d\.]+)\s*$`) + + matches := re.FindStringSubmatch(s) + if matches == nil { + return 0, 0, fmt.Errorf("Cannot parse 'HD' string: %s", s) + } + + lat, err := strconv.ParseFloat(matches[2], 64) + if err != nil || lat > 90 { + return 0, 0, fmt.Errorf("Cannot parse 'HD' string: %s", s) + } + if matches[1] == "S" || matches[1] == "s" { + lat = -lat + } + + lng, err := strconv.ParseFloat(matches[4], 64) + if err != nil || lng > 180 { + return 0, 0, fmt.Errorf("Cannot parse 'HD' string: %s", s) + } + if matches[3] == "W" || matches[3] == "w" { + lng = -lng + } + + return lat, lng, nil +} + +// ParseHDM parses a coordinate string of the form "H D M.MMM H D M.MMM" and returns a lat/lng pair or an error +func ParseHDM(s string) (float64, float64, error) { + re := regexp.MustCompile(`^\s*([NnSs])\s*([\d]+)\s+([\d.]+)\s+([EeWw])\s*([\d]+)\s+([\d.]+)\s*$`) + + matches := re.FindStringSubmatch(s) + if matches == nil { + return 0, 0, fmt.Errorf("Cannot parse 'HDM' string: %s", s) + } + + latDeg, err := strconv.ParseFloat(matches[2], 64) + if err != nil || latDeg > 90 { + return 0, 0, fmt.Errorf("Cannot parse 'HDM' string: %s", s) + } + latMin, err := strconv.ParseFloat(matches[3], 64) + if err != nil || latMin >= 60 { + return 0, 0, fmt.Errorf("Cannot parse 'HDM' string: %s", s) + } + lat := latDeg + latMin/60.0 + if matches[1] == "S" || matches[1] == "s" { + lat = -lat + } + + lngDeg, err := strconv.ParseFloat(matches[5], 64) + if err != nil || lngDeg > 180 { + return 0, 0, fmt.Errorf("Cannot parse 'HDM' string: %s", s) + } + lngMin, err := strconv.ParseFloat(matches[6], 64) + if err != nil || lngMin >= 60 { + return 0, 0, fmt.Errorf("Cannot parse 'HDM' string: %s", s) + } + lng := lngDeg + lngMin/60.0 + if matches[4] == "W" || matches[4] == "w" { + lng = -lng + } + + return lat, lng, nil +} + +// ParseHDMS parses a coordinate string of the form "H D M S.SSS H D M S.SSS" and returns a lat/lng pair or an error +func ParseHDMS(s string) (float64, float64, error) { + re := regexp.MustCompile(`^\s*([NnSs])\s*([\d]+)\s+([\d]+)\s+([\d.]+)\s+([EeWw])\s*([\d]+)\s+([\d]+)\s+([\d.]+)\s*$`) + + matches := re.FindStringSubmatch(s) + if matches == nil { + return 0, 0, fmt.Errorf("Cannot parse 'HDMS' string: %s", s) + } + + latDeg, err := strconv.ParseFloat(matches[2], 64) + if err != nil || latDeg > 90 { + return 0, 0, fmt.Errorf("Cannot parse 'HDMS' string: %s", s) + } + latMin, err := strconv.ParseFloat(matches[3], 64) + if err != nil || latMin >= 60 { + return 0, 0, fmt.Errorf("Cannot parse 'HDMS' string: %s", s) + } + latSec, err := strconv.ParseFloat(matches[4], 64) + if err != nil || latSec >= 60 { + return 0, 0, fmt.Errorf("Cannot parse 'HDMS' string: %s", s) + } + lat := latDeg + latMin/60.0 + latSec/3600.0 + if matches[1] == "S" || matches[1] == "s" { + lat = -lat + } + + lngDeg, err := strconv.ParseFloat(matches[6], 64) + if err != nil || lngDeg > 180 { + return 0, 0, fmt.Errorf("Cannot parse 'HDMS' string: %s", s) + } + lngMin, err := strconv.ParseFloat(matches[7], 64) + if err != nil || lngMin >= 60 { + return 0, 0, fmt.Errorf("Cannot parse 'HDMS' string: %s", s) + } + lngSec, err := strconv.ParseFloat(matches[8], 64) + if err != nil || lngSec >= 60 { + return 0, 0, fmt.Errorf("Cannot parse 'HDMS' string: %s", s) + } + lng := lngDeg + lngMin/60.0 + lngSec/3600.0 + if matches[5] == "W" || matches[5] == "w" { + lng = -lng + } + + return lat, lng, nil +} diff --git a/vendor/github.com/flopp/go-staticmaps/LICENSE b/vendor/github.com/flopp/go-staticmaps/LICENSE new file mode 100644 index 0000000..7417b3d --- /dev/null +++ b/vendor/github.com/flopp/go-staticmaps/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Florian Pigorsch + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/flopp/go-staticmaps/area.go b/vendor/github.com/flopp/go-staticmaps/area.go new file mode 100644 index 0000000..387105a --- /dev/null +++ b/vendor/github.com/flopp/go-staticmaps/area.go @@ -0,0 +1,94 @@ +// 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 + +import ( + "image/color" + "strconv" + "strings" + + "github.com/flopp/go-coordsparser" + "github.com/fogleman/gg" + "github.com/golang/geo/s2" +) + +// Area represents a area or area on the map +type Area struct { + MapObject + Positions []s2.LatLng + Color color.Color + Fill color.Color + Weight float64 +} + +// ParseAreaString parses a string and returns an area +func ParseAreaString(s string) (*Area, error) { + area := new(Area) + area.Color = color.RGBA{0xff, 0, 0, 0xff} + area.Fill = color.Transparent + area.Weight = 5.0 + + for _, ss := range strings.Split(s, "|") { + if ok, suffix := hasPrefix(ss, "color:"); ok { + var err error + area.Color, err = ParseColorString(suffix) + if err != nil { + return nil, err + } + } else if ok, suffix := hasPrefix(ss, "fill:"); ok { + var err error + area.Fill, err = ParseColorString(suffix) + if err != nil { + return nil, err + } + } else if ok, suffix := hasPrefix(ss, "weight:"); ok { + var err error + area.Weight, err = strconv.ParseFloat(suffix, 64) + if err != nil { + return nil, err + } + } else { + lat, lng, err := coordsparser.Parse(ss) + if err != nil { + return nil, err + } + area.Positions = append(area.Positions, s2.LatLngFromDegrees(lat, lng)) + } + + } + return area, nil +} + +func (p *Area) extraMarginPixels() float64 { + return 0.5 * p.Weight +} + +func (p *Area) bounds() s2.Rect { + r := s2.EmptyRect() + for _, ll := range p.Positions { + r = r.AddPoint(ll) + } + return r +} + +func (p *Area) draw(gc *gg.Context, trans *transformer) { + if len(p.Positions) <= 1 { + return + } + + gc.ClearPath() + gc.SetLineWidth(p.Weight) + gc.SetLineCap(gg.LineCapRound) + gc.SetLineJoin(gg.LineJoinRound) + for _, ll := range p.Positions { + gc.LineTo(trans.ll2p(ll)) + } + gc.ClosePath() + gc.SetColor(p.Fill) + gc.FillPreserve() + gc.SetColor(p.Color) + gc.Stroke() +} diff --git a/vendor/github.com/flopp/go-staticmaps/color.go b/vendor/github.com/flopp/go-staticmaps/color.go new file mode 100644 index 0000000..ea73ffd --- /dev/null +++ b/vendor/github.com/flopp/go-staticmaps/color.go @@ -0,0 +1,65 @@ +// 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 + +import ( + "fmt" + "image/color" + "regexp" + "strings" +) + +// ParseColorString parses hex color strings (i.e. `0xRRGGBB`, `#RRGGBB`, `0xRRGGBBAA`, `#RRGGBBAA`), and names colors (e.g. 'black', 'blue', ...) +func ParseColorString(s string) (color.Color, error) { + s = strings.ToLower(strings.TrimSpace(s)) + + re := regexp.MustCompile(`^(0x|#)([A-Fa-f0-9]{6})$`) + matches := re.FindStringSubmatch(s) + if matches != nil { + var r, g, b int + fmt.Sscanf(matches[2], "%2x%2x%2x", &r, &g, &b) + return color.RGBA{uint8(r), uint8(g), uint8(b), 0xff}, nil + } + + re = regexp.MustCompile(`^(0x|#)([A-Fa-f0-9]{8})$`) + matches = re.FindStringSubmatch(s) + if matches != nil { + var r, g, b, a int + fmt.Sscanf(matches[2], "%2x%2x%2x%2x", &r, &g, &b, &a) + rr := float64(r) * float64(a) / 256.0 + gg := float64(g) * float64(a) / 256.0 + bb := float64(b) * float64(a) / 256.0 + return color.RGBA{uint8(rr), uint8(gg), uint8(bb), uint8(a)}, nil + } + + switch s { + case "black": + return color.RGBA{0x00, 0x00, 0x00, 0xff}, nil + case "blue": + return color.RGBA{0x00, 0x00, 0xff, 0xff}, nil + case "brown": + return color.RGBA{0x96, 0x4b, 0x00, 0xff}, nil + case "green": + return color.RGBA{0x00, 0xff, 0x00, 0xff}, nil + case "orange": + return color.RGBA{0xff, 0x7f, 0x00, 0xff}, nil + case "purple": + return color.RGBA{0x7f, 0x00, 0x7f, 0xff}, nil + case "red": + return color.RGBA{0xff, 0x00, 0x00, 0xff}, nil + case "yellow": + return color.RGBA{0xff, 0xff, 0x00, 0xff}, nil + case "white": + return color.RGBA{0xff, 0xff, 0xff, 0xff}, nil + } + return color.Transparent, fmt.Errorf("Cannot parse color string: %s", s) +} + +// Luminance computes the luminance (~ brightness) of the given color. Range: 0.0 for black to 1.0 for white. +func Luminance(col color.Color) float64 { + r, g, b, _ := col.RGBA() + return (float64(r)*0.299 + float64(g)*0.587 + float64(b)*0.114) / float64(0xffff) +} 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 +} diff --git a/vendor/github.com/flopp/go-staticmaps/create-static-map/create-static-map.go b/vendor/github.com/flopp/go-staticmaps/create-static-map/create-static-map.go new file mode 100644 index 0000000..7b8d429 --- /dev/null +++ b/vendor/github.com/flopp/go-staticmaps/create-static-map/create-static-map.go @@ -0,0 +1,134 @@ +// 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 main + +import ( + "fmt" + "log" + "os" + "sort" + + "github.com/flopp/go-coordsparser" + "github.com/flopp/go-staticmaps" + "github.com/fogleman/gg" + "github.com/golang/geo/s2" + "github.com/jessevdk/go-flags" +) + +func getTileProviderOrExit(name string) *sm.TileProvider { + tileProviders := sm.GetTileProviders() + tp := tileProviders[name] + if tp != nil { + return tp + } + + if name != "list" { + fmt.Println("Bad map type:", name) + } + fmt.Println("Possible map types (to be used with --type/-t):") + // print sorted keys + keys := make([]string, 0, len(tileProviders)) + for k := range tileProviders { + keys = append(keys, k) + } + sort.Strings(keys) + for _, k := range keys { + fmt.Println(k) + } + os.Exit(0) + + return nil +} + +func main() { + var opts struct { + // ClearCache bool `long:"clear-cache" description:"Clears the tile cache"` + Width int `long:"width" description:"Width of the generated static map image" value-name:"PIXELS" default:"512"` + Height int `long:"height" description:"Height of the generated static map image" value-name:"PIXELS" default:"512"` + Output string `short:"o" long:"output" description:"Output file name" value-name:"FILENAME" default:"map.png"` + Type string `short:"t" long:"type" description:"Select the map type; list possible map types with '--type list'" value-name:"MAPTYPE"` + Center string `short:"c" long:"center" description:"Center coordinates (lat,lng) of the static map" value-name:"LATLNG"` + Zoom int `short:"z" long:"zoom" description:"Zoom factor" value-name:"ZOOMLEVEL"` + Markers []string `short:"m" long:"marker" description:"Add a marker to the static map" value-name:"MARKER"` + Paths []string `short:"p" long:"path" description:"Add a path to the static map" value-name:"PATH"` + Areas []string `short:"a" long:"area" description:"Add an area to the static map" value-name:"AREA"` + } + + parser := flags.NewParser(&opts, flags.HelpFlag|flags.PassDoubleDash) + parser.LongDescription = `Creates a static map` + _, err := parser.Parse() + + if parser.FindOptionByLongName("help").IsSet() { + parser.WriteHelp(os.Stdout) + os.Exit(0) + } + + ctx := sm.NewContext() + + if parser.FindOptionByLongName("type").IsSet() { + tp := getTileProviderOrExit(opts.Type) + if tp != nil { + ctx.SetTileProvider(tp) + } + } + + ctx.SetSize(opts.Width, opts.Height) + + if parser.FindOptionByLongName("zoom").IsSet() { + ctx.SetZoom(opts.Zoom) + } + + if parser.FindOptionByLongName("center").IsSet() { + lat, lng, err := coordsparser.Parse(opts.Center) + if err != nil { + log.Fatal(err) + } else { + ctx.SetCenter(s2.LatLngFromDegrees(lat, lng)) + } + } + + for _, markerString := range opts.Markers { + markers, err := sm.ParseMarkerString(markerString) + if err != nil { + log.Fatal(err) + } else { + for _, marker := range markers { + ctx.AddMarker(marker) + } + } + } + + for _, pathString := range opts.Paths { + paths, err := sm.ParsePathString(pathString) + if err != nil { + log.Fatal(err) + } else { + for _, path := range paths { + ctx.AddPath(path) + } + } + } + + for _, areaString := range opts.Areas { + area, err := sm.ParseAreaString(areaString) + if err != nil { + log.Fatal(err) + } else { + ctx.AddArea(area) + } + } + + img, err := ctx.Render() + if err != nil { + log.Fatal(err) + return + } + + if err = gg.SavePNG(opts.Output, img); err != nil { + log.Fatal(err) + return + } +} diff --git a/vendor/github.com/flopp/go-staticmaps/map_object.go b/vendor/github.com/flopp/go-staticmaps/map_object.go new file mode 100644 index 0000000..d174b51 --- /dev/null +++ b/vendor/github.com/flopp/go-staticmaps/map_object.go @@ -0,0 +1,13 @@ +package sm + +import ( + "github.com/fogleman/gg" + "github.com/golang/geo/s2" +) + +// MapObject is the interface for all objects on the map +type MapObject interface { + bounds() s2.Rect + extraMarginPixels() float64 + draw(dc *gg.Context, trans *transformer) +} diff --git a/vendor/github.com/flopp/go-staticmaps/marker.go b/vendor/github.com/flopp/go-staticmaps/marker.go new file mode 100644 index 0000000..6e88984 --- /dev/null +++ b/vendor/github.com/flopp/go-staticmaps/marker.go @@ -0,0 +1,125 @@ +// 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 + +import ( + "fmt" + "image/color" + "math" + "strconv" + "strings" + + "github.com/flopp/go-coordsparser" + "github.com/fogleman/gg" + "github.com/golang/geo/s2" +) + +// Marker represents a marker on the map +type Marker struct { + MapObject + Position s2.LatLng + Color color.Color + Size float64 + Label string +} + +// NewMarker creates a new Marker +func NewMarker(pos s2.LatLng, col color.Color, size float64) *Marker { + m := new(Marker) + m.Position = pos + m.Color = col + m.Size = size + m.Label = "" + return m +} + +func parseSizeString(s string) (float64, error) { + switch { + case s == "mid": + return 16.0, nil + case s == "small": + return 12.0, nil + case s == "tiny": + return 8.0, nil + } + + if ss, err := strconv.ParseFloat(s, 64); err != nil && ss > 0 { + return ss, nil + } + + return 0.0, fmt.Errorf("Cannot parse size string: %s", s) +} + +// ParseMarkerString parses a string and returns an array of markers +func ParseMarkerString(s string) ([]*Marker, error) { + markers := make([]*Marker, 0, 0) + + var color color.Color = color.RGBA{0xff, 0, 0, 0xff} + size := 16.0 + label := "" + + for _, ss := range strings.Split(s, "|") { + if ok, suffix := hasPrefix(ss, "color:"); ok { + var err error + color, err = ParseColorString(suffix) + if err != nil { + return nil, err + } + } else if ok, suffix := hasPrefix(ss, "label:"); ok { + label = suffix + } else if ok, suffix := hasPrefix(ss, "size:"); ok { + var err error + size, err = parseSizeString(suffix) + if err != nil { + return nil, err + } + } else { + lat, lng, err := coordsparser.Parse(ss) + if err != nil { + return nil, err + } + m := NewMarker(s2.LatLngFromDegrees(lat, lng), color, size) + m.Label = label + markers = append(markers, m) + } + } + return markers, nil +} + +func (m *Marker) extraMarginPixels() float64 { + return 1.0 + 1.5*m.Size +} + +func (m *Marker) bounds() s2.Rect { + r := s2.EmptyRect() + r = r.AddPoint(m.Position) + return r +} + +func (m *Marker) draw(gc *gg.Context, trans *transformer) { + gc.ClearPath() + gc.SetLineJoin(gg.LineJoinRound) + gc.SetLineWidth(1.0) + + radius := 0.5 * m.Size + x, y := trans.ll2p(m.Position) + gc.DrawArc(x, y-m.Size, radius, (90.0+60.0)*math.Pi/180.0, (360.0+90.0-60.0)*math.Pi/180.0) + gc.LineTo(x, y) + gc.ClosePath() + gc.SetColor(m.Color) + gc.FillPreserve() + gc.SetRGB(0, 0, 0) + gc.Stroke() + + if m.Label != "" { + if Luminance(m.Color) >= 0.5 { + gc.SetColor(color.RGBA{0x00, 0x00, 0x00, 0xff}) + } else { + gc.SetColor(color.RGBA{0xff, 0xff, 0xff, 0xff}) + } + gc.DrawStringAnchored(m.Label, x, y-m.Size, 0.5, 0.5) + } +} diff --git a/vendor/github.com/flopp/go-staticmaps/path.go b/vendor/github.com/flopp/go-staticmaps/path.go new file mode 100644 index 0000000..1c580e6 --- /dev/null +++ b/vendor/github.com/flopp/go-staticmaps/path.go @@ -0,0 +1,103 @@ +// 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 + +import ( + "image/color" + "strconv" + "strings" + + "github.com/flopp/go-coordsparser" + "github.com/fogleman/gg" + "github.com/golang/geo/s2" + "github.com/tkrajina/gpxgo/gpx" +) + +// Path represents a path or area on the map +type Path struct { + MapObject + Positions []s2.LatLng + Color color.Color + Weight float64 +} + +// ParsePathString parses a string and returns a path +func ParsePathString(s string) ([]*Path, error) { + paths := make([]*Path, 0, 0) + currentPath := new(Path) + currentPath.Color = color.RGBA{0xff, 0, 0, 0xff} + currentPath.Weight = 5.0 + + for _, ss := range strings.Split(s, "|") { + if ok, suffix := hasPrefix(ss, "color:"); ok { + var err error + if currentPath.Color, err = ParseColorString(suffix); err != nil { + return nil, err + } + } else if ok, suffix := hasPrefix(ss, "weight:"); ok { + var err error + if currentPath.Weight, err = strconv.ParseFloat(suffix, 64); err != nil { + return nil, err + } + } else if ok, suffix := hasPrefix(ss, "gpx:"); ok { + gpxData, err := gpx.ParseFile(suffix) + if err != nil { + return nil, err + } + for _, trk := range gpxData.Tracks { + for _, seg := range trk.Segments { + p := new(Path) + p.Color = currentPath.Color + p.Weight = currentPath.Weight + for _, pt := range seg.Points { + p.Positions = append(p.Positions, s2.LatLngFromDegrees(pt.GetLatitude(), pt.GetLongitude())) + } + if len(p.Positions) > 0 { + paths = append(paths, p) + } + } + } + } else { + lat, lng, err := coordsparser.Parse(ss) + if err != nil { + return nil, err + } + currentPath.Positions = append(currentPath.Positions, s2.LatLngFromDegrees(lat, lng)) + } + } + if len(currentPath.Positions) > 0 { + paths = append(paths, currentPath) + } + return paths, nil +} + +func (p *Path) extraMarginPixels() float64 { + return 0.5 * p.Weight +} + +func (p *Path) bounds() s2.Rect { + r := s2.EmptyRect() + for _, ll := range p.Positions { + r = r.AddPoint(ll) + } + return r +} + +func (p *Path) draw(gc *gg.Context, trans *transformer) { + if len(p.Positions) <= 1 { + return + } + + gc.ClearPath() + gc.SetLineWidth(p.Weight) + gc.SetLineCap(gg.LineCapRound) + gc.SetLineJoin(gg.LineJoinRound) + for _, ll := range p.Positions { + gc.LineTo(trans.ll2p(ll)) + } + gc.SetColor(p.Color) + gc.Stroke() +} diff --git a/vendor/github.com/flopp/go-staticmaps/tile_fetcher.go b/vendor/github.com/flopp/go-staticmaps/tile_fetcher.go new file mode 100644 index 0000000..e02a71f --- /dev/null +++ b/vendor/github.com/flopp/go-staticmaps/tile_fetcher.go @@ -0,0 +1,153 @@ +// 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 + +import ( + "bytes" + "fmt" + "image" + _ "image/jpeg" // to be able to decode jpegs + _ "image/png" // to be able to decode pngs + "io" + "io/ioutil" + "log" + "net/http" + "os" + "path/filepath" + + "github.com/Wessie/appdirs" +) + +// TileFetcher downloads map tile images from a TileProvider +type TileFetcher struct { + tileProvider *TileProvider + cacheDir string + useCaching bool +} + +// NewTileFetcher creates a new Tilefetcher struct +func NewTileFetcher(tileProvider *TileProvider) *TileFetcher { + t := new(TileFetcher) + t.tileProvider = tileProvider + app := appdirs.New("go-staticmaps", "flopp.net", "0.1") + t.cacheDir = fmt.Sprintf("%s/%s", app.UserCache(), tileProvider.Name) + t.useCaching = true + return t +} + +func (t *TileFetcher) url(zoom, x, y int) string { + shard := "" + ss := len(t.tileProvider.Shards) + if len(t.tileProvider.Shards) > 0 { + shard = t.tileProvider.Shards[(x+y)%ss] + } + return t.tileProvider.getURL(shard, zoom, x, y) +} + +func (t *TileFetcher) cacheFileName(zoom int, x, y int) string { + return fmt.Sprintf("%s/%d/%d/%d", t.cacheDir, zoom, x, y) +} + +// ToggleCaching enables/disables caching +func (t *TileFetcher) ToggleCaching(enabled bool) { + t.useCaching = enabled +} + +// Fetch download (or retrieves from the cache) a tile image for the specified zoom level and tile coordinates +func (t *TileFetcher) Fetch(zoom, x, y int) (image.Image, error) { + if t.useCaching { + fileName := t.cacheFileName(zoom, x, y) + cachedImg, err := t.loadCache(fileName) + if err == nil { + return cachedImg, nil + } + } + + url := t.url(zoom, x, y) + data, err := t.download(url) + if err != nil { + return nil, err + } + + img, _, err := image.Decode(bytes.NewBuffer(data)) + if err != nil { + return nil, err + } + + if t.useCaching { + fileName := t.cacheFileName(zoom, x, y) + if err := t.storeCache(fileName, data); err != nil { + log.Printf("Failed to store map tile as '%s': %s", fileName, err) + } + } + + return img, nil +} + +func (t *TileFetcher) download(url string) ([]byte, error) { + resp, err := http.Get(url) + defer resp.Body.Close() + + if err != nil { + return nil, err + } + + contents, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + return contents, nil +} + +func (t *TileFetcher) loadCache(fileName string) (image.Image, error) { + file, err := os.Open(fileName) + if err != nil { + return nil, err + } + defer file.Close() + + img, _, err := image.Decode(file) + if err != nil { + return nil, err + } + + return img, nil +} + +func (t *TileFetcher) createCacheDir(path string) error { + src, err := os.Stat(path) + if err != nil { + if os.IsNotExist(err) { + return os.MkdirAll(path, 0777) + } + return err + } + if src.IsDir() { + return nil + } + return fmt.Errorf("File exists but is not a directory: %s", path) +} + +func (t *TileFetcher) storeCache(fileName string, data []byte) error { + dir, _ := filepath.Split(fileName) + + if err := t.createCacheDir(dir); err != nil { + return err + } + + file, err := os.Create(fileName) + if err != nil { + return err + } + defer file.Close() + + if _, err = io.Copy(file, bytes.NewBuffer(data)); err != nil { + return err + } + + return nil +} diff --git a/vendor/github.com/flopp/go-staticmaps/tile_provider.go b/vendor/github.com/flopp/go-staticmaps/tile_provider.go new file mode 100644 index 0000000..e54400c --- /dev/null +++ b/vendor/github.com/flopp/go-staticmaps/tile_provider.go @@ -0,0 +1,98 @@ +// 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 + +import "fmt" + +// TileProvider encapsulates all infos about a map tile provider service (name, url scheme, attribution, etc.) +type TileProvider struct { + Name string + Attribution string + TileSize int + URLPattern string // "%[1]s" => shard, "%[2]d" => zoom, "%[3]d" => x, "%[4]d" => y + Shards []string +} + +func (t *TileProvider) getURL(shard string, zoom, x, y int) string { + return fmt.Sprintf(t.URLPattern, shard, zoom, x, y) +} + +// NewTileProviderMapQuest creates a TileProvider struct for mapquest's tile service +func NewTileProviderMapQuest() *TileProvider { + t := new(TileProvider) + t.Name = "mapquest" + t.Attribution = "Maps (c) MapQuest; Data (c) OSM and contributors, ODbL" + t.TileSize = 256 + t.URLPattern = "http://otile%[1]s.mqcdn.com/tiles/1.0.0/osm/%[2]d/%[3]d/%[4]d.png" + t.Shards = []string{"1", "2", "3", "4"} + return t +} + +func newTileProviderThunderforest(name string) *TileProvider { + t := new(TileProvider) + t.Name = fmt.Sprintf("thunderforest-%s", name) + t.Attribution = "Maps (c) Thundeforest; Data (c) OSM and contributors, ODbL" + t.TileSize = 256 + t.URLPattern = "https://%[1]s.tile.thunderforest.com/" + name + "/%[2]d/%[3]d/%[4]d.png" + t.Shards = []string{"a", "b", "c"} + return t +} + +// NewTileProviderThunderforestLandscape creates a TileProvider struct for thundeforests's 'landscape' tile service +func NewTileProviderThunderforestLandscape() *TileProvider { + return newTileProviderThunderforest("landscape") +} + +// NewTileProviderThunderforestOutdoors creates a TileProvider struct for thundeforests's 'outdoors' tile service +func NewTileProviderThunderforestOutdoors() *TileProvider { + return newTileProviderThunderforest("outdoors") +} + +// NewTileProviderThunderforestTransport creates a TileProvider struct for thundeforests's 'transport' tile service +func NewTileProviderThunderforestTransport() *TileProvider { + return newTileProviderThunderforest("transport") +} + +// NewTileProviderStamenToner creates a TileProvider struct for stamens' 'toner' tile service +func NewTileProviderStamenToner() *TileProvider { + t := new(TileProvider) + t.Name = "stamen-toner" + t.Attribution = "Maps (c) Stamen; Data (c) OSM and contributors, ODbL" + t.TileSize = 256 + t.URLPattern = "http://%[1]s.tile.stamen.com/toner/%[2]d/%[3]d/%[4]d.png" + t.Shards = []string{"a", "b", "c", "d"} + return t +} + +// NewTileProviderOpenTopoMap creates a TileProvider struct for opentopomaps's tile service +func NewTileProviderOpenTopoMap() *TileProvider { + t := new(TileProvider) + t.Name = "opentopomap" + t.Attribution = "Maps (c) OpenTopoMap [CC-BY-SA]; Data (c) OSM and contributors [ODbL]; Data (c) SRTM" + t.TileSize = 256 + t.URLPattern = "http://%[1]s.tile.opentopomap.org/%[2]d/%[3]d/%[4]d.png" + t.Shards = []string{"a", "b", "c"} + return t +} + +// GetTileProviders returns a map of all available TileProviders +func GetTileProviders() map[string]*TileProvider { + m := make(map[string]*TileProvider) + + list := []*TileProvider{ + NewTileProviderMapQuest(), + NewTileProviderThunderforestLandscape(), + NewTileProviderThunderforestOutdoors(), + NewTileProviderThunderforestTransport(), + NewTileProviderStamenToner(), + NewTileProviderOpenTopoMap()} + + for _, tp := range list { + m[tp.Name] = tp + } + + return m +} diff --git a/vendor/github.com/flopp/go-staticmaps/util.go b/vendor/github.com/flopp/go-staticmaps/util.go new file mode 100644 index 0000000..3f5d4ad --- /dev/null +++ b/vendor/github.com/flopp/go-staticmaps/util.go @@ -0,0 +1,18 @@ +// 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 + +import ( + "strings" +) + +// hasPrefix checks if 's' has prefix 'prefix'; returns 'true' and the remainder on success, and 'false', 's' otherwise. +func hasPrefix(s string, prefix string) (bool, string) { + if strings.HasPrefix(s, prefix) { + return true, strings.TrimPrefix(s, prefix) + } + return false, s +} -- cgit v1.2.3